Сравнительный анализ небольших примеров кода в C #, можно ли улучшить эту реализацию? - PullRequest
102 голосов
/ 26 июня 2009

Довольно часто на SO я обнаруживаю, что сравниваю небольшие куски кода, чтобы увидеть, какая реализация наиболее быстрая.

Довольно часто я вижу комментарии о том, что тестовый код не учитывает джиттинг или сборщик мусора.

У меня есть следующая простая функция бенчмаркинга, которую я медленно развивал:

  static void Profile(string description, int iterations, Action func) {
        // warm up 
        func();
        // clean up
        GC.Collect();

        var watch = new Stopwatch();
        watch.Start();
        for (int i = 0; i < iterations; i++) {
            func();
        }
        watch.Stop();
        Console.Write(description);
        Console.WriteLine(" Time Elapsed {0} ms", watch.ElapsedMilliseconds);
    }

Использование:

Profile("a descriptions", how_many_iterations_to_run, () =>
{
   // ... code being profiled
});

Есть ли в этой реализации недостатки? Достаточно ли хорошо, чтобы показать, что реализация X быстрее, чем реализация Y по Z итераций? Можете ли вы придумать, как бы вы могли это улучшить?

EDIT Совершенно очевидно, что предпочтителен подход, основанный на времени (в отличие от итераций), есть ли у кого-нибудь реализации, где проверки времени не влияют на производительность?

Ответы [ 11 ]

0 голосов
/ 20 сентября 2014

Основная проблема с вашим вопросом заключается в предположении, что один Измерение может ответить на все ваши вопросы. Вам нужно измерить несколько раз, чтобы получить эффективную картину ситуации и особенно в языке сборки мусора типа C #.

Другой ответ дает хороший способ измерения базовой производительности.

static void Profile(string description, int iterations, Action func) {
    // warm up 
    func();

    var watch = new Stopwatch(); 

    // clean up
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    watch.Start();
    for (int i = 0; i < iterations; i++) {
        func();
    }
    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
}

Однако, это единственное измерение не учитывает мусор коллекция. Правильный профиль дополнительно учитывает наихудшую производительность сбора мусора распределены по многим вызовам (этот номер бесполезно, так как виртуальная машина может завершиться, даже не собирая оставшиеся мусор, но все еще полезно для сравнения двух разных реализации func.)

static void ProfileGarbageMany(string description, int iterations, Action func) {
    // warm up 
    func();

    var watch = new Stopwatch(); 

    // clean up
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    watch.Start();
    for (int i = 0; i < iterations; i++) {
        func();
    }
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
}

И можно также измерить наихудшую производительность сборка мусора для метода, который вызывается только один раз.

static void ProfileGarbage(string description, int iterations, Action func) {
    // warm up 
    func();

    var watch = new Stopwatch(); 

    // clean up
    GC.Collect();
    GC.WaitForPendingFinalizers();
    GC.Collect();

    watch.Start();
    for (int i = 0; i < iterations; i++) {
        func();

        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
    }
    watch.Stop();
    Console.Write(description);
    Console.WriteLine(" Time Elapsed {0} ms", watch.Elapsed.TotalMilliseconds);
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...