Обертывание тайминга StopWatch с помощью делегата или лямбда?

95

Я пишу такой код, немного быстро и грязно:

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

Конечно , есть способ вызвать этот бит кода синхронизации как лямбда фантазийных schmancy .NET 3.0 , а не ( не дай Бог) копируя и вставляя его несколько раз и замена DoStuff(s)с DoSomethingElse(s)?

Я знаю, что это можно сделать, Delegateно мне интересно, как работает лямбда.

Джефф Этвуд
источник

Ответы:

129

Как насчет расширения класса Stopwatch?

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));

Вы можете добавить еще одну перегрузку, которая опускает параметр «итераций» и вызывает эту версию с некоторым значением по умолчанию (например, 1000).

Мэтт Гамильтон
источник
3
Вы можете заменить sw.Start () на sw.StartNew (), чтобы предотвратить случайное увеличение прошедшего времени для каждого последовательного вызова s.Time (), повторно используя тот же экземпляр Stopwatch.
VVS,
11
@Jay Я согласен с тем, что «foreach» с Enumerable.Range выглядит немного более «современным», но мои тесты показывают, что он примерно в четыре раза медленнее, чем цикл «for» при большом количестве. YMMV.
Мэтт Гамильтон,
2
-1: Использование расширения класса здесь не имеет смысла. Timeведет себя как статический метод, отбрасывая все существующее состояние sw, поэтому введение его в качестве метода экземпляра выглядит просто бесполезным.
ildjarn
2
@ildjam Я ценю, что вы оставили комментарий, объясняющий свой отрицательный голос, но я думаю, что вы неправильно понимаете идею методов расширения.
Мэтт Гамильтон,
4
@Matt Hamilton: Я так не думаю - они для добавления (логически) методов экземпляра к существующим классам. Но это не более чем метод экземпляра Stopwatch.StartNew, который по какой-то причине статичен. В C # отсутствует возможность добавлять статические методы к существующим классам (в отличие от F #), поэтому я понимаю импульс к этому, но это все еще оставляет неприятный привкус во рту.
ildjarn 02
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 - Я уверен, что вы с этим справились, но в операторе using отсутствовала скобка
Алекс
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);

В противном случае этот ответ выглядит так, как будто он имеет приличную «общую» способность:

Обертывание тайминга StopWatch с помощью делегата или лямбда?

Марк Ингрэм
источник
круто, но меня не волнует, как это связано с конкретным классом или базовым классом; можно ли это сделать в более общем плане?
Джефф Этвуд,
Как в классе MyObject, для которого написан метод расширения? Его можно легко изменить, чтобы расширить класс Object или другой класс в дереве наследования.
Марк Ингрэм,
Я думал более статично, как будто не привязан к ЛЮБОМУ конкретному объекту или классу ... время и время вроде универсальны
Джефф Этвуд
Отлично, вторая версия - это больше, чем я думал, +1, но я принимаю Мэтта, поскольку он дошел до нее первым.
Джефф Этвуд,
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);
}

Я протестировал статические методы и методы расширения (объединение итераций и тестов), и разница между ожидаемым временем выполнения и реальным временем выполнения составляет <= 1 мс.

Энтони Мастрян
источник
От версий метода расширения у меня слюнки текут. :)
bzlm
7

Некоторое время назад я написал простой класс CodeProfiler, который обернул секундомер, чтобы легко профилировать метод с помощью Action: 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; }
    }
}
Jyoung
источник
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)
Сэм Шафран
источник
1

Мне нравится использовать классы CodeTimer от Вэнса Моррисона (одного из тех, кто занимается производительностью из .NET).

В своем блоге он опубликовал сообщение под названием « Быстрое и легкое измерение управляемого кода: CodeTimers. ».

Он включает в себя такие классные вещи, как MultiSampleCodeTimer. Он автоматически рассчитывает среднее значение и стандартное отклонение, а также очень легко распечатать результаты.

Дэви Лэндман
источник
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;
    }
}
Альпер Эбикоглу
источник