델리게이트 또는 람다로 스톱워치 타이밍을 래핑 하시겠습니까?


95

나는 약간 빠르고 더러운 타이밍을 사용하여 이와 같은 코드를 작성하고 있습니다.

var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < 1000; i++)
{
    b = DoStuff(s);
}
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);

확실히이 타이밍 코드를 몇 번 자르고 붙여넣고 DoStuff(s)DoSomethingElse(s)?

나는 그것이 할 수 있다는 것을 알고 Delegate있지만 람다 방식에 대해 궁금합니다.

답변:


129

스톱워치 클래스를 확장하는 것은 어떻습니까?

public static class StopwatchExtensions
{
    public static long Time(this Stopwatch sw, Action action, int iterations)
    {
        sw.Reset();
        sw.Start(); 
        for (int i = 0; i < iterations; i++)
        {
            action();
        }
        sw.Stop();

        return sw.ElapsedMilliseconds;
    }
}

그런 다음 다음과 같이 호출하십시오.

var s = new Stopwatch();
Console.WriteLine(s.Time(() => DoStuff(), 1000));

"iterations"매개 변수를 생략하고 일부 기본값 (예 : 1000)으로이 버전을 호출하는 또 다른 오버로드를 추가 할 수 있습니다.


3
sw.Start ()를 sw.StartNew ()로 대체하여 동일한 스톱워치 인스턴스를 재사용하여 s.Time ()을 연속적으로 호출 할 때마다 경과 시간이 실수로 증가하는 것을 방지 할 수 있습니다.
VVS

11
@Jay Enumerable.Range의 "foreach"가 좀 더 "현대적"인 것처럼 보이지만 내 테스트에 따르면 많은 수에 걸쳐 "for"루프보다 약 4 배 느립니다. YMMV.
Matt Hamilton

2
-1 : 여기서 클래스 확장을 사용하는 것은 의미가 없습니다. Time는 정적 메서드로 작동하여에서 기존 상태를 모두 삭제 sw하므로 인스턴스 메서드로 도입하면 변덕스러워 보입니다.
ildjarn 2011 년

2
@ildjam 비추천에 대해 설명하는 댓글을 남겨 주셔서 감사합니다.하지만 확장 방법에 대한 아이디어를 오해하고 계신 것 같습니다.
Matt Hamilton

4
@Matt Hamilton : 저는 그렇게 생각하지 않습니다. 기존 클래스에 (논리적으로) 인스턴스 메서드를 추가하기위한 것입니다. 그러나 이것은 Stopwatch.StartNew이유 때문에 정적 인 것보다 더 이상 인스턴스 메서드가 아닙니다 . C #은 기존 클래스에 정적 메서드를 추가하는 기능이 없기 때문에 (F #과 달리) 이렇게해야하는 충동을 이해하지만 여전히 입안에 나쁜 맛을 남깁니다.
ildjarn 2011

32

내가 사용한 것은 다음과 같습니다.

public class DisposableStopwatch: IDisposable {
    private readonly Stopwatch sw;
    private readonly Action<TimeSpan> f;

    public DisposableStopwatch(Action<TimeSpan> f) {
        this.f = f;
        sw = Stopwatch.StartNew();
    }

    public void Dispose() {
        sw.Stop();
        f(sw.Elapsed);
    }
}

용법:

using (new DisposableStopwatch(t => Console.WriteLine("{0} elapsed", t))) {
  // do stuff that I want to measure
}

이것은 내가 본 최고의 솔루션입니다! 확장이없고 (많은 클래스에서 사용할 수 있도록) 매우 깨끗합니다!
Calvin

사용 예제가 올바르게 있는지 확실하지 않습니다. Console.WriteLine("")에서 테스트 를 위해 일부를 사용하려고 // do stuff that I want to measure하면 컴파일러가 전혀 행복하지 않습니다. 거기에서 정상적인 표현과 진술을해야합니까?
Tim

@Tim-나는 당신이 그것을 해결했다고 확신하지만 using 문에 누락 된 대괄호가 있습니다
Alex

12

사용중인 클래스 (또는 기본 클래스)에 대해 확장 메서드를 작성해 볼 수 있습니다.

전화는 다음과 같습니다.

Stopwatch sw = MyObject.TimedFor(1000, () => DoStuff(s));

그런 다음 확장 방법 :

public static Stopwatch TimedFor(this DependencyObject source, Int32 loops, Action action)
{
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < loops; ++i)
{
    action.Invoke();
}
sw.Stop();

return sw;
}

DependencyObject에서 파생 된 모든 개체는 이제 TimedFor (..)를 호출 할 수 있습니다. 함수는 ref 매개 변수를 통해 반환 값을 제공하도록 쉽게 조정할 수 있습니다.

-

기능이 클래스 / 객체에 연결되는 것을 원하지 않는 경우 다음과 같이 할 수 있습니다.

public class Timing
{
  public static Stopwatch TimedFor(Action action, Int32 loops)
  {
    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < loops; ++i)
    {
      action.Invoke();
    }
    sw.Stop();

    return sw;
  }
}

그런 다음 다음과 같이 사용할 수 있습니다.

Stopwatch sw = Timing.TimedFor(() => DoStuff(s), 1000);

실패하면이 답변은 괜찮은 "일반적인"능력을 가지고있는 것처럼 보입니다.

델리게이트 또는 람다로 스톱워치 타이밍을 래핑 하시겠습니까?


멋지지만 특정 클래스 나 기본 클래스에 연결되는 방식은 신경 쓰지 않습니다. 더 일반적으로 할 수 있습니까?
Jeff Atwood

확장 메서드가 작성된 MyObject 클래스에서와 같이? 상속 트리에서 Object 클래스 또는 다른 클래스를 확장하도록 쉽게 변경할 수 있습니다.
Mark Ingram

나는 어떤 특정 객체 나 클래스에 묶이지 않는 것처럼 좀 더 정적 인 생각을하고 있었다. 시간과 타이밍은 일종의 보편적이다
Jeff Atwood

훌륭합니다. 두 번째 버전은 제가 생각했던 것보다 더 많았습니다. +1하지만 Matt가 처음에 도달했을 때 수락했습니다.
Jeff Atwood

7

StopWatch클래스 일 필요는 없습니다 Disposed또는 Stopped오류에. 그래서, 간단한 코드에 시간이 몇 가지 조치가 있다

public partial class With
{
    public static long Benchmark(Action action)
    {
        var stopwatch = Stopwatch.StartNew();
        action();
        stopwatch.Stop();
        return stopwatch.ElapsedMilliseconds;
    }
}

샘플 호출 코드

public void Execute(Action action)
{
    var time = With.Benchmark(action);
    log.DebugFormat(“Did action in {0} ms.”, time);
}

StopWatch코드에 반복을 포함하는 아이디어가 마음에 들지 않습니다 . N반복 실행을 처리하는 다른 메서드 또는 확장을 언제든지 만들 수 있습니다 .

public partial class With
{
    public static void Iterations(int n, Action action)
    {
        for(int count = 0; count < n; count++)
            action();
    }
}

샘플 호출 코드

public void Execute(Action action, int n)
{
    var time = With.Benchmark(With.Iterations(n, action));
    log.DebugFormat(“Did action {0} times in {1} ms.”, n, time);
}

다음은 확장 메서드 버전입니다.

public static class Extensions
{
    public static long Benchmark(this Action action)
    {
        return With.Benchmark(action);
    }

    public static Action Iterations(this Action action, int n)
    {
        return () => With.Iterations(n, action);
    }
}

그리고 샘플 호출 코드

public void Execute(Action action, int n)
{
    var time = action.Iterations(n).Benchmark()
    log.DebugFormat(“Did action {0} times in {1} ms.”, n, time);
}

정적 메서드와 확장 메서드 (반복 및 벤치 마크 결합)를 테스트했으며 예상 실행 시간과 실제 실행 시간의 델타는 <= 1ms입니다.


확장 방법 버전은 내 입에 물을줍니다. :)
bzlm

7

Action을 사용하여 메서드를 쉽게 프로파일 링하기 위해 Stopwatch를 래핑 한 간단한 CodeProfiler 클래스를 얼마 전에 작성했습니다. http://www.improve.dk/blog/2008/04/16/profiling-code-the-easy-way

또한 다중 스레드 코드를 쉽게 프로파일 링 할 수 있습니다. 다음 예제는 1-16 개의 스레드로 작업 람다를 프로파일 링합니다.

static void Main(string[] args)
{
    Action action = () =>
    {
        for (int i = 0; i < 10000000; i++)
            Math.Sqrt(i);
    };

    for(int i=1; i<=16; i++)
        Console.WriteLine(i + " thread(s):\t" + 
            CodeProfiler.ProfileAction(action, 100, i));

    Console.Read();
}

4

한 가지 빠른 타이밍이 필요하다고 가정하면 사용하기 쉽습니다.

  public static class Test {
    public static void Invoke() {
        using( SingleTimer.Start )
            Thread.Sleep( 200 );
        Console.WriteLine( SingleTimer.Elapsed );

        using( SingleTimer.Start ) {
            Thread.Sleep( 300 );
        }
        Console.WriteLine( SingleTimer.Elapsed );
    }
}

public class SingleTimer :IDisposable {
    private Stopwatch stopwatch = new Stopwatch();

    public static readonly SingleTimer timer = new SingleTimer();
    public static SingleTimer Start {
        get {
            timer.stopwatch.Reset();
            timer.stopwatch.Start();
            return timer;
        }
    }

    public void Stop() {
        stopwatch.Stop();
    }
    public void Dispose() {
        stopwatch.Stop();
    }

    public static TimeSpan Elapsed {
        get { return timer.stopwatch.Elapsed; }
    }
}

2

여러 메서드를 오버로드하여 람다에 전달할 수있는 다양한 매개 변수 사례를 처리 할 수 ​​있습니다.

public static Stopwatch MeasureTime<T>(int iterations, Action<T> action, T param)
{
    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < iterations; i++)
    {
        action.Invoke(param);
    }
    sw.Stop();

    return sw;
}

public static Stopwatch MeasureTime<T, K>(int iterations, Action<T, K> action, T param1, K param2)
{
    var sw = new Stopwatch();
    sw.Start();
    for (int i = 0; i < iterations; i++)
    {
        action.Invoke(param1, param2);
    }
    sw.Stop();

    return sw;
}

또는 값을 반환해야하는 경우 Func 대리자를 사용할 수 있습니다. 각 반복이 고유 한 값을 사용해야하는 경우 매개 변수의 배열 (또는 그 이상)을 전달할 수도 있습니다.


2

나에게 확장 프로그램은 int에서 조금 더 직관적으로 느껴지므로 더 이상 스톱워치를 인스턴스화하거나 재설정하는 것에 대해 걱정할 필요가 없습니다.

그래서 당신은 :

static class BenchmarkExtension {

    public static void Times(this int times, string description, Action action) {
        Stopwatch watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < times; i++) {
            action();
        }
        watch.Stop();
        Console.WriteLine("{0} ... Total time: {1}ms ({2} iterations)", 
            description,  
            watch.ElapsedMilliseconds,
            times);
    }
}

샘플 사용법 :

var randomStrings = Enumerable.Range(0, 10000)
    .Select(_ => Guid.NewGuid().ToString())
    .ToArray();

50.Times("Add 10,000 random strings to a Dictionary", 
    () => {
        var dict = new Dictionary<string, object>();
        foreach (var str in randomStrings) {
            dict.Add(str, null);
        }
    });

50.Times("Add 10,000 random strings to a SortedList",
    () => {
        var list = new SortedList<string, object>();
        foreach (var str in randomStrings) {
            list.Add(str, null);
        }
    });

샘플 출력 :

Add 10,000 random strings to a Dictionary ... Total time: 144ms (50 iterations)
Add 10,000 random strings to a SortedList ... Total time: 4088ms (50 iterations)


0
public static class StopWatchExtensions
{
    public static async Task<TimeSpan> LogElapsedMillisecondsAsync(
        this Stopwatch stopwatch,
        ILogger logger,
        string actionName,
        Func<Task> action)
    {
        stopwatch.Reset();
        stopwatch.Start();

        await action();

        stopwatch.Stop();

        logger.LogDebug(string.Format(actionName + " completed in {0}.", stopwatch.Elapsed.ToString("hh\\:mm\\:ss")));

        return stopwatch.Elapsed;
    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.