Перенос времени секундомера с помощью делегата или лямбды?

StackOverflow https://stackoverflow.com/questions/232848

  •  04-07-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

Как насчет расширения класса секундомеров?

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

Другие советы

Вот что я использовал:

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
}

Вы могли бы попробовать написать метод расширения для любого используемого вами класса (или любого базового класса).

Я бы хотел, чтобы звонок выглядел как:

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 класс не обязательно должен быть 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 мс.

Некоторое время назад я написал простой класс CodeProfiler, который обернул Stopwatch, чтобы легко профилировать метод с помощью действия:http://www.improve.dk/blog/2008/04/16/profiling-code-the-easy-way

Это также легко позволит вам профилировать многопоточный код.В следующем примере будет задан профиль лямбда-выражения action с 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();
}

Предполагая, что вам просто нужно быстро выбрать время для чего-то одного, это простое в использовании приложение.

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

Вы можете перегрузить несколько методов, чтобы охватить различные варианты параметров, которые вы, возможно, захотите передать в лямбда-выражение:

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

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

Для меня расширение кажется немного более интуитивно понятным в 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)

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

Он сделал пост в своем блоге под названием "Быстрое и простое измерение управляемого кода:Таймеры кода".

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top