Завершение сеанса StopWatch с помощью делегата или лямбда?
Я пишу код вроде этого, делая немного быстрого и грязного времени:
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
, но мне интересно о лямбда-пути.
Ответы
Ответ 1
Как насчет расширения класса секундомера?
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).
Ответ 2
Вот что я использовал:
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
}
Ответ 3
Вы можете попробовать написать метод расширения для любого класса, который вы используете (или любого базового класса).
Я бы назвал вызов:
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);
В противном случае этот ответ выглядит как у него есть приличная "общая" способность:
Завершение синхронизации секундомера с помощью делегата или лямбда?
Ответ 4
Я написал простой класс 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();
}
Ответ 5
Класс 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 мс.
Ответ 6
Предполагая, что вам просто нужно быстро выбрать одну вещь, это легко использовать.
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; }
}
}
Ответ 7
Для меня расширение кажется немного интуитивным в 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)
Ответ 8
Вы можете перегрузить ряд методов, чтобы охватить различные случаи параметров, которые вы, возможно, захотите передать в лямбда:
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, если они должны вернуть значение. Вы также можете передать массив (или больше) параметров, если каждая итерация должна использовать уникальное значение.
Ответ 9
Мне нравится использовать классы CodeTimer из Vance Morrison (один из разработчиков производительности .NET).
Он опубликовал сообщение в своем блоге под названием "" Быстрое и легкое измерение управляемого кода: CodeTimers".
Он включает классные вещи, такие как MultiSampleCodeTimer. Он выполняет автоматический расчет среднего и стандартного отклонения, а также очень легко распечатать ваши результаты.