Обгортання часу секундоміра з делегатом чи лямбда?


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

Як щодо розширення класу Секундомір?

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 (), повторного використання того самого екземпляра секундоміра.
VVS

11
@Jay Я погоджуюсь, що "foreach" з Enumerable.Range виглядає дещо більш "сучасним", але мої тести показують, що це приблизно в чотири рази повільніше, ніж цикл "for" при великому рахунку. YMMV.
Метт Гамільтон,

2
-1: Використовувати розширення класу тут немає сенсу. Timeповодиться як статичний метод, відкидаючи весь існуючий стан sw, тому введення його як методу екземпляра просто виглядає хитро.
ildjarn

2
@ildjam Я вдячний, що ви залишили коментар із поясненням свого голосу проти, але я думаю, ви не розумієте ідеї методів розширення.
Метт Гамільтон,

4
@Matt Hamilton: Я не думаю - вони для додавання (логічно) методів екземплярів до існуючих класів. Але це не більше ніж метод екземпляра, ніж Stopwatch.StartNewстатичний з певної причини. У C # не вистачає можливості додавати статичні методи до існуючих класів (на відміну від F #), тому я розумію імпульс для цього, але він все одно залишає неприємний смак у роті.
ildjarn

31

Ось що я використовував:

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
}

Це найкраще рішення, яке я коли-небудь бачив! Без розширення (так що його можна використовувати на багатьох заняттях) і дуже чисто!
Кальвін

Я не впевнений, чи правильно отримав приклад використання. Коли я намагаюся використовувати деякі 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);

Якщо цього не вдасться, ця відповідь схожа на те, що вона має якусь гідну "загальну" здатність:

Обгортання часу секундоміра з делегатом чи лямбда?


круто, але мені байдуже, як це пов’язано з певним класом або базовим класом; чи можна зробити це більш загально?
Джефф Етвуд,

Як у класі 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; }
    }
}

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 від Vance Morrison (одного з чуваків продуктивності з .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;
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.