Загальна характеристика порівняно з не-загальною в C #

Я написав два еквівалентні методи:

static bool F(T a, T b) where T : class
{
    return a == b;
}

static bool F2(A a, A b)
{
    return a == b;
}

Time difference:
00:00:00.0380022
00:00:00.0170009

Код для тестування:

var a = new A();
for (int i = 0; i < 100000000; i++)
    F(a, a);
Console.WriteLine(DateTime.Now - dt);

dt = DateTime.Now;
for (int i = 0; i < 100000000; i++)
    F2(a, a);
Console.WriteLine(DateTime.Now - dt);

Хто-небудь знає, чому?

У коментарі нижче dtb * показуйте CIL

IL for F2: ldarg.0, ldarg.1, ceq, ret. IL for F: ldarg.0, box !!T, ldarg.1, box !!T, ceq, ret.

Я думаю, що це відповідь на моє питання, але яку магію я можу використати, щоб заперечити бокс?

Далі я використовую код з Psilon :

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication58
{
    internal class Program
    {
        private class A
        {

        }

        private static bool F(T a, T b) where T : class
        {
            return a == b;
        }

        private static bool F2(A a, A b)
        {
            return a == b;
        }

        private static void Main()
        {
            const int rounds = 100, n = 10000000;
            var a = new A();
            var fList = new List();
            var f2List = new List();
            for (int i = 0; i < rounds; i++)
            {
               //Test generic
                GCClear();
                bool res;
                var sw = new Stopwatch();
                sw.Start();
                for (int j = 0; j < n; j++)
                {
                    res = F(a, a);
                }
                sw.Stop();
                fList.Add(sw.Elapsed);

               //Test not-generic
                GCClear();
                bool res2;
                var sw2 = new Stopwatch();
                sw2.Start();
                for (int j = 0; j < n; j++)
                {
                    res2 = F2(a, a);
                }
                sw2.Stop();
                f2List.Add(sw2.Elapsed);
            }
            double f1AverageTicks = fList.Average(ts => ts.Ticks);
            Console.WriteLine("Elapsed for F = {0} \t ticks = {1}", fList.Average(ts => ts.TotalMilliseconds),
                              f1AverageTicks);
            double f2AverageTicks = f2List.Average(ts => ts.Ticks);
            Console.WriteLine("Elapsed for F2 = {0} \t ticks = {1}", f2List.Average(ts => ts.TotalMilliseconds),
                  f2AverageTicks);
            Console.WriteLine("Not-generic method is {0} times faster, or on {1}%", f1AverageTicks/f2AverageTicks,
                              (f1AverageTicks/f2AverageTicks - 1)*100);
            Console.ReadKey();
        }

        private static void GCClear()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }
    }
}

Windows 7, .NET 4.5, Visual Studio 2012, release, optimized, without attaching.

x64

Elapsed for F = 23.68157         ticks = 236815.7
Elapsed for F2 = 1.701638        ticks = 17016.38
Not-generic method is 13.916925926666 times faster, or on 1291.6925926666%

x86

Elapsed for F = 6.713223         ticks = 67132.23
Elapsed for F2 = 6.729897        ticks = 67298.97
Not-generic method is 0.997522398931217 times faster, or on -0.247760106878314%

А у мене з'явилася нова магія: x64 в три рази швидше ...

PS: Моя цільова платформа - x64.

23
@Matthew ви не можете бачити накладання на рівень IL. Це трюк JIT. Це збірка.
додано Автор Steven, джерело
@Matthew ви не можете бачити накладання на рівень IL. Це трюк JIT. Це збірка.
додано Автор Steven, джерело
Чи відбувається зміна замовлення? Останні команди зазвичай виконуються набагато швидше через jit-компіляцію і через кешування.
додано Автор Willem Van Onsem, джерело
Чи відбувається зміна замовлення? Останні команди зазвичай виконуються набагато швидше через jit-компіляцію і через кешування.
додано Автор Willem Van Onsem, джерело
Ви перевірили IL? Другий приклад може бути вбудований компілятором.
додано Автор Matthew, джерело
Ви перевірили IL? Другий приклад може бути вбудований компілятором.
додано Автор Matthew, джерело
@dtb: Ви дивитеся на IL, що не має значення. Потрібно подивитися на рідний код, який генерує JIT.
додано Автор Ben Voigt, джерело
@dtb: Ви дивитеся на IL, що не має значення. Потрібно подивитися на рідний код, який генерує JIT.
додано Автор Ben Voigt, джерело
Це значною мірою залежить від системи: наприклад, у режимі Mono ви отримуєте майже однаковий час роботи . Ви можете додати неіменований виклик до F (a, a); перед тим, як почати перший таймер, щоб побачити, чи він навіть вийшов.
додано Автор dasblinkenlight, джерело
Це значною мірою залежить від системи: наприклад, у режимі Mono ви отримуєте майже однаковий час роботи . Ви можете додати неіменований виклик до F (a, a); перед тим, як почати перший таймер, щоб побачити, чи він навіть вийшов.
додано Автор dasblinkenlight, джерело
@frugi: ні, це синтаксичний цукор, компілятор не хвилює.
додано Автор Dan Puzey, джерело
@dtb: Ви впевнені, що вони вписані навіть у де T: class ?
додано Автор Dan Puzey, джерело
@dtb: Ви впевнені, що вони вписані навіть у де T: class ?
додано Автор Dan Puzey, джерело
Я отримую 00: 00: 00.2080244 і 00: 00: 00.0071957, використовуючи секундомір у версії Release без налагоджувача.
додано Автор dtb, джерело
Я отримую 00: 00: 00.2080244 і 00: 00: 00.0071957, використовуючи секундомір у версії Release без налагоджувача.
додано Автор dtb, джерело
IL для F2: ldarg.0, ldarg.1, ceq, ret . IL для F : ldarg.0, поле !! T, ldarg.1, поле !! T, ceq, ret . Отже, відповідь тому, що аргументи отримують в коробці.
додано Автор dtb, джерело
@DanPuzey: Так. Це також дивує мене. Я використовую Visual Studio 2012 і ILspy.
додано Автор dtb, джерело
@DanPuzey: Так. Це також дивує мене. Я використовую Visual Studio 2012 і ILspy.
додано Автор dtb, джерело
IL для F2: ldarg.0, ldarg.1, ceq, ret . IL для F : ldarg.0, поле !! T, ldarg.1, поле !! T, ceq, ret . Отже, відповідь тому, що аргументи отримують в коробці.
додано Автор dtb, джерело
Різниця швидкості 13 коефіцієнта є іншою помилкою вимірювання. Метод, що не є загальним, був укладений, а загальний метод - не в x64. Сформований код збірки ідентичний. Якщо ви не дивитесь глибше, ви розкриєте одну таємницю над іншою, але ви не дізнаєтеся, чому ваша програма поводиться по-іншому.
додано Автор Alois Kraus, джерело
Різниця швидкості 13 коефіцієнта є іншою помилкою вимірювання. Метод, що не є загальним, був укладений, а загальний метод - не в x64. Сформований код збірки ідентичний. Якщо ви не дивитесь глибше, ви розкриєте одну таємницю над іншою, але ви не дізнаєтеся, чому ваша програма поводиться по-іншому.
додано Автор Alois Kraus, джерело
Я дивуюся, що ви намагаєтеся визначити відмінності в коді IL, тоді як згенерований код збірки ідентичний. Будь-яка різниця продуктивності, яку ви будете вимірювати, за визначенням є помилкою вимірювання.
додано Автор Alois Kraus, джерело
Я дивуюся, що ви намагаєтеся визначити відмінності в коді IL, тоді як згенерований код збірки ідентичний. Будь-яка різниця продуктивності, яку ви будете вимірювати, за визначенням є помилкою вимірювання.
додано Автор Alois Kraus, джерело
Отримав 27ms для першого та 27ms для другого, використовуючи секундомір у випуску випуску з розігрівом. У debug (котрий ми не дбаємо про) я приїхав 1177 та 1139ms. Крім того, у F (a, a) є зайвим згідно ReSharper.
додано Автор Pierre-Luc Pineault, джерело
Отримав 27ms для першого та 27ms для другого, використовуючи секундомір у випуску випуску з розігрівом. У debug (котрий ми не дбаємо про) я приїхав 1177 та 1139ms. Крім того, у F (a, a) є зайвим згідно ReSharper.
додано Автор Pierre-Luc Pineault, джерело
Використовуйте Секундомір , починаючи після ініціалізації A .
додано Автор SimpleVar, джерело
Використовуйте Секундомір , починаючи після ініціалізації A .
додано Автор SimpleVar, джерело
Чи існує відмінність продуктивності між "var a = new A ();" і "A a = new A ();"?
додано Автор frugi, джерело
Чи існує відмінність продуктивності між "var a = new A ();" і "A a = new A ();"?
додано Автор frugi, джерело
я використав .net 4.5, і він вбудовує обидва методи; dtb, я думаю, якщо ви додасте [MethodImpl (MethodImplOptions.AggressiveInlining)] - ви будете мати ті ж результати
додано Автор Dmitry, джерело
я використав .net 4.5, і він вбудовує обидва методи; dtb, я думаю, якщо ви додасте [MethodImpl (MethodImplOptions.AggressiveInlining)] - ви будете мати ті ж результати
додано Автор Dmitry, джерело

8 Відповіді

Я зробив деякі зміни у вашому коді, щоб правильно виміряти перфо.

  1. Використовуйте секундомір
  2. Виконати режим випуску
  3. Заборонити вбудовування.
  4. Використовуйте GetHashCode (), щоб виконати деяку справжню роботу
  5. Перегляньте згенерований код збірки

Ось код:

class A
{
}

[MethodImpl(MethodImplOptions.NoInlining)]
static bool F(T a, T b) where T : class
{
    return a.GetHashCode() == b.GetHashCode();
}

[MethodImpl(MethodImplOptions.NoInlining)]
static bool F2(A a, A b)
{
    return a.GetHashCode() == b.GetHashCode();
}

static int Main(string[] args)
{
    const int Runs = 100 * 1000 * 1000;
    var a = new A();
    bool lret = F(a, a);
    var sw = Stopwatch.StartNew();
    for (int i = 0; i < Runs; i++)
    {
        F(a, a);
    }
    sw.Stop();
    Console.WriteLine("Generic: {0:F2}s", sw.Elapsed.TotalSeconds);

    lret = F2(a, a);
    sw = Stopwatch.StartNew();
    for (int i = 0; i < Runs; i++)
    {
        F2(a, a);
    }
    sw.Stop();
    Console.WriteLine("Non Generic: {0:F2}s", sw.Elapsed.TotalSeconds);

    return lret ? 1 : 0;
}

Під час моїх тестів не родова версія була трохи швидше (.NET 4.5 x32 Windows 7). Але різниці в швидкості практично немає. Я б сказав, що обидва рівні. Для повноти тут є код збірки загальної версії: Я отримав код збірки за допомогою відладчика в режимі випуску з включеною оптимізацією JIT. За замовчуванням відключити оптимізацію JIT під час налагодження для полегшення встановлення контрольних точок і змінних.

Загальний

static bool F(T a, T b) where T : class
{
        return a.GetHashCode() == b.GetHashCode();
}

push        ebp 
mov         ebp,esp 
push        ebx 
sub         esp,8//reserve stack for two locals 
mov         dword ptr [ebp-8],ecx//store first arg on stack
mov         dword ptr [ebp-0Ch],edx//store second arg on stack
mov         ecx,dword ptr [ebp-8]//get first arg from stack --> stupid!
mov         eax,dword ptr [ecx]  //load MT pointer from a instance
mov         eax,dword ptr [eax+28h]//Locate method table start
call        dword ptr [eax+8] //GetHashCode//call GetHashCode function pointer which is the second method starting from the method table
mov         ebx,eax          //store result in ebx
mov         ecx,dword ptr [ebp-0Ch]//get second arg
mov         eax,dword ptr [ecx]    //call method as usual ...
mov         eax,dword ptr [eax+28h] 
call        dword ptr [eax+8] //GetHashCode
cmp         ebx,eax 
sete        al 
movzx       eax,al 
lea         esp,[ebp-4] 
pop         ebx 
pop         ebp 
ret         4 

Non Generic

static bool F2(A a, A b)
{
  return a.GetHashCode() == b.GetHashCode();
}

push        ebp 
mov         ebp,esp 
push        esi 
push        ebx 
mov         esi,edx 
mov         eax,dword ptr [ecx] 
mov         eax,dword ptr [eax+28h] 
call        dword ptr [eax+8] //GetHashCode
mov         ebx,eax 
mov         ecx,esi 
mov         eax,dword ptr [ecx] 
mov         eax,dword ptr [eax+28h] 
call        dword ptr [eax+8] //GetHashCode
cmp         ebx,eax 
sete        al 
movzx       eax,al 
pop         ebx 
pop         esi 
pop         ebp 
ret 

Як ви можете бачити, загальна версія виглядає трохи більш неефективною через більшу кількість операцій, які не є досконалими, але насправді різниця не піддається вимірюванню, оскільки все вписується в кеш L1 процесора, що робить операції пам'яті менш витратними порівняно з операції чистого реєстру невідомої версії. Я підозрюю, що не єдиною версією слід працювати трохи краще в реальному світі, якщо вам потрібно платити за реальний доступ до пам'яті, який не надходить з кеш-пам'яті процесора.

Для всіх практичних цілей ці обидва методи ідентичні. Ви повинні подивитися на якесь інше місце для реальних світових показників продуктивності. Я б спочатку подивився на моделі доступу до даних і використані структури даних. Алгоритмічні зміни, як правило, приносять набагато більше переваг, ніж такі, що мають низький рівень.

Edit1: Якщо ви хочете використовувати ==, тоді ви знайдете

00000000  push        ebp 
00000001  mov         ebp,esp 
00000003  cmp         ecx,edx//Check for reference equality 
00000005  sete        al 
00000008  movzx       eax,al 
0000000b  pop         ebp 
0000000c  ret         4 

обидва методи дають точно той же машинний код. Будь-яка різниця, яку ви виміряли, - це ваші помилки вимірювання.

18
додано
Хто піклується про IL? Машинний код однаковий.
додано Автор Alois Kraus, джерело
Див. Моє редагування. Сформований код збірки для обох методів ідентичний. Усі попередні плакати, які роздумували про відмінності в коді IL та інших впливають факторів ніколи не дивилися на гостро виконуваний код. На рівні збірки немає різниці! Будь-яка відмінність в перфо - це просто помилка вимірювання.
додано Автор Alois Kraus, джерело
спасибі, але я зацікавлений в тому, як швидко моє додаток, я не хочу використовувати. \ t
додано Автор Dmitry, джерело
Цікаво, у мене є різниця ІЛ, див
додано Автор Dmitry, джерело

Перестаньте турбуватися про терміни, турбуйтеся про правильність.

Ці методи не є еквівалентними. Один з них використовує клас < оператор == , а інший використовує об'єкт 's оператор == .

5
додано
@dtb: у цьому запиті нічого не сказано, чи має клас A оператор == ... і принаймні деякі можливі загальні аргументи, результат буде іншим .
додано Автор Ben Voigt, джерело
@dtb: Так, загальний має викликати за допомогою операторів System.Object .
додано Автор Ben Voigt, джерело
@dtb Ну, якщо A або будь-який з перевантажених супер-типів operator == . Якщо об'єкт є найбільш похідним типом у спадковій ланцюжку, який перевантажує == , то він буде використовувати його.
додано Автор Servy, джерело
F використовує оператор об'єкта ==, а F2 використовує оператор класу A ==, так?
додано Автор dtb, джерело
Тому, якщо оператор перевантажень класу A ==, обидва використовують оператор об'єкта == і, таким чином, є еквівалентом.
додано Автор dtb, джерело
Я не взяв питання про необхідність потурбувати про дію, але як допитливий про що продовжується під кришкою. Цілком дивно, що два методи, які обидва тесту для рівноправності посилань виконують так по-різному; випадок, коли A перевизначення == тут не цікаво. Я подивився на код збірки x64, згенерований для обох методів, і не може виявити будь-якої великої різниці, але, можливо, я роблю це неправильно.
додано Автор dtb, джерело

Ваш метод тестування є недосконалим. Є кілька великих проблем з тим, як ви це зробили.

По-перше, ви не надали " розминки " . У .NET перший раз, коли ви отримуєте доступ до чогось, це буде повільніше, ніж наступні виклики, щоб він міг завантажувати будь-які необхідні збірки. Якщо ви збираєтеся виконувати такі тести, ви повинні виконувати кожну функцію, принаймні один раз, або перший тест, який потрібно виконати, зазнає великого штрафу. Йдіть вперед і поміняйте порядок, ви, швидше за все, побачите протилежні результати.

Second DateTime is only accurate to 16ms, so when comparing two times you have a +/- error of 32 ms. The difference between the two results are 21 ms, well within the experimental error. You must use a more accurate timer like the Stopwatch class.

Нарешті, не робіть штучних тестів, як це. Вони не показують вам жодної корисної інформації, крім прав хвастощів для одного чи іншого класу. Замість цього скористайтеся кодом-профілі . Це покаже вам, що уповільнює ваш код, і ви можете приймати обґрунтовані рішення про те, як вирішити проблему, а не "вгадати", що невикористання шаблонного класу зробить ваш код швидшим.

Нижче наведено приклад коду, який показує, як він повинен бути виконаний:

using System;
using System.Diagnostics;

namespace Sandbox_Console
{
    class A
    {
    }

    internal static class Program
    {
        static bool F(T a, T b) where T : class
        {
            return a == b;
        }

        static bool F2(A a, A b)
        {
            return a == b;
        }

        private static void Main()
        {
            var a = new A();
            Stopwatch st = new Stopwatch();

            Console.WriteLine("warmup");
            st.Start();
            for (int i = 0; i < 100000000; i++)
                F(a, a);
            Console.WriteLine(st.Elapsed);

            st.Restart();
            for (int i = 0; i < 100000000; i++)
                F2(a, a);
            Console.WriteLine(st.Elapsed);

            Console.WriteLine("real");
            st.Restart();
            for (int i = 0; i < 100000000; i++)
                F(a, a);
            Console.WriteLine(st.Elapsed);

            st.Restart();
            for (int i = 0; i < 100000000; i++)
                F2(a, a);
            Console.WriteLine(st.Elapsed);

            Console.WriteLine("Done");
            Console.ReadLine();
        }

    }
}

А ось результати:

warmup
00:00:00.0297904
00:00:00.0298949
real
00:00:00.0296838
00:00:00.0297823
Done

Зміна порядку двох останніх перших завжди коротша, тому вони ефективні, так само як і в експериментальній помилці.

4
додано
@dtb Я повинен не погодитися з вашими результатами, я тільки що опублікував мої результати, і я отримую різницю в 0,1 мс, а перша завжди виграє. Ви зробили щось не так, що зробили перший запуск дуже повільним.
додано Автор Scott Chamberlain, джерело
Що ви використовуєте для A ? клас (як у мене) або щось на зразок int (що насправді є структурою)? Також ви працюєте в режимі випуску без додавання відладчика? Прикріплення відладчика значно вплине на результати, оскільки вимикає багато вбудованих оптимізаторів, які мають справу зі збором сміття (тому що, якщо ви призупинили програму і хотіли побачити, яке значення змінної було у вікні перегляду, можна t нехай змінна виходить за рамки так легко, коли додається відладчик).
додано Автор Scott Chamberlain, джерело
Цікаво .... Повторно запустіть код як x86 і подивіться, що відбувається;). Я поняття не маю, чому будівництво, як x64, сповільнить один з двох.
додано Автор Scott Chamberlain, джерело
Хороша порада, але не відповідь. Я перевірив результати за допомогою секундоміра, розминки і т.д. Див. Мій коментар вище.
додано Автор dtb, джерело
Запуск коду як x86 змінює результати на ті, що ви бачите (обидва значення приблизно однакові, і повільніше, ніж x64). Цікаво.
додано Автор dtb, джерело
спасибі, хороші поради
додано Автор Dmitry, джерело
розминка
00: 00: 00.2611852
00: 00: 00.0265555
реальне
00: 00: 00.2700937
00: 00: 00.0181213
win7, vs 12 , .net 4.5, x64
додано Автор Dmitry, джерело
@ Скотт Чемберлен ваш код, ваш А, випуск, без прикріплення,
додано Автор Dmitry, джерело

Я переписав код тесту:

var stopwatch = new Stopwatch();
var a = new A();

stopwatch.Reset();
stopwatch.Start();
for (int i = 0; i < 100000000; i++)
    F(a, a);
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);

stopwatch.Reset();
stopwatch.Start();
for (int i = 0; i < 100000000; i++)
    F2(a, a);
stopwatch.Stop();
Console.WriteLine(stopwatch.ElapsedMilliseconds);

Зміна замовлення не змінює нічого.

CIL for generic method:

L_0000: nop
L_0001: ldarg.0
L_0002: box !!T
L_0007: ldarg.1
L_0008: box !!T
L_000d: ceq
L_000f: stloc.0
L_0010: br.s L_0012
L_0012: ldloc.0
L_0013: ret

А для загальних:

L_0000: nop
L_0001: ldarg.0
L_0002: ldarg.1
L_0003: ceq
L_0005: stloc.0
L_0006: br.s L_0008
L_0008: ldloc.0
L_0009: ret

So the boxing operation is the reason of your time difference. The question is why the boxing operation is added. Check it, Stack Overflow question Boxing when using generics in C#

3
додано
@Dmitry: C# не має шаблонів. Генератори .NET не є подібними шаблонами C ++ (принаймні стосовно цього питання).
додано Автор Ben Voigt, джерело
Але як дії в коді впливають на код, який генерується JIT (тобто кодом, який процесор фактично виконує)?
додано Автор Ben Voigt, джерело
Так, як я можу зупинити бокс в шаблонах?
додано Автор Dmitry, джерело

Дві речі:

  1. Ви встановлюєте порівняльний аналіз з DateTime.Now . Замість цього використовуйте Секундомір .
  2. Ви запускаєте код, який не працює за нормальних умов. JIT, швидше за все, впливає на перший запуск, роблячи ваш перший метод повільніше.

Якщо ви змінюєте порядок тестів (тобто перевіряєте спочатку метод, який не є загальним), чи повертається результат? Я підозрюю так. Коли я під’єднав ваш код до LINQPad , а потім скопіював його, щоб він виконав обидва тести двічі , час виконання для другої ітерації знаходився в межах декількох сотень кліщів один одного.

Отже, у відповідь на ваше запитання: так, хтось знає, чому. Це тому, що ваш тест є неточним!

3
додано
Порядок нічого не змінює. Проблема полягає в бокс-операції в загальному методі
додано Автор nirmus, джерело
@nirmus: Тут немає жодного боксу. Для посилальних типів інструкція не робить жодних дій. Його буде вилучено СІТ.
додано Автор Ben Voigt, джерело

Я кілька разів у своїй кар'єрі виконував аналіз продуктивності у професійній якості і мав кілька спостережень.

  • По-перше, тест занадто короткий, щоб бути дійсним. Моє правило полягає в тому, що тест продуктивності повинен тривати 30 хвилин або близько того.
  • По-друге, важливо багато разів запускати тест, щоб отримати діапазон часу.
  • По-третє, я здивований, що компілятор не оптимізував петлі, оскільки результати функцій не використовуються, а викликані функції не мають побічних ефектів.
  • По-четверте, мікро-тести часто вводять в оману.

Я колись працював над командою компіляторів, яка мала велику зухвалість. В одній зборці була введена оптимізація, яка усунула кілька інструкцій для конкретної послідовності. Вона повинна мати поліпшену продуктивність, але замість цього продуктивність одного еталона різко впала. Ми працювали на апаратному забезпеченні з прямим відображенням кешу. Виявляється, код для циклу і функція, яка викликається у внутрішньому циклі, займали ту ж саму лінію кешу з новою оптимізацією на місці, але не з попереднім генерованим кодом. Іншими словами, цей тест дійсно був еталоном пам'яті і повністю залежав від хітів і пропусків кеш-пам'яті, тоді як автори вважали, що вони написали обчислювальний тест.

2
додано

Це виглядає більш справедливим, ні?: D

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

namespace ConsoleApplication58
{
    internal class Program
    {
        private class A
        {

        }

        private static bool F(T a, T b) where T : class
        {
            return a == b;
        }

        private static bool F2(A a, A b)
        {
            return a == b;
        }

        private static void Main()
        {
            const int rounds = 100, n = 10000000;
            var a = new A();
            var fList = new List();
            var f2List = new List();
            for (int i = 0; i < rounds; i++)
            {
                //test generic
                GCClear();
                bool res;
                var sw = new Stopwatch();
                sw.Start();
                for (int j = 0; j < n; j++)
                {
                    res = F(a, a);
                }
                sw.Stop();
                fList.Add(sw.Elapsed);

                //test not-generic
                GCClear();
                bool res2;
                var sw2 = new Stopwatch();
                sw2.Start();
                for (int j = 0; j < n; j++)
                {
                    res2 = F2(a, a);
                }
                sw2.Stop();
                f2List.Add(sw2.Elapsed);
            }
            double f1AverageTicks = fList.Average(ts => ts.Ticks);
            Console.WriteLine("Elapsed for F = {0} \t ticks = {1}", fList.Average(ts => ts.TotalMilliseconds),
                              f1AverageTicks);
            double f2AverageTicks = f2List.Average(ts => ts.Ticks);
            Console.WriteLine("Elapsed for F2 = {0} \t ticks = {1}", f2List.Average(ts => ts.TotalMilliseconds),
                  f2AverageTicks);
            Console.WriteLine("Not-generic method is {0} times faster, or on {1}%", f1AverageTicks/f2AverageTicks,
                              (f1AverageTicks/f2AverageTicks - 1)*100);
            Console.ReadKey();
        }

        private static void GCClear()
        {
            GC.Collect();
            GC.WaitForPendingFinalizers();
            GC.Collect();
        }
    }
}

На моєму ноутбуці i7-3615qm загальний код швидше , ніж звичайний.

Див. http://ideone.com/Y1GIJK .

1
додано
Які параметри компіляції ви використовували? Назва файлу передбачає, що це побудова Debug, без оптимізації, що не цікаво.
додано Автор Ben Voigt, джерело
Для чесного тестування я розмістив його на ideone, ви можете перевірити його самостійно. Що стосується оптимізації - я не використовував його, тому що компілятор не може компілювати цикл із змінною, яка не використовується ніде. А також ви можете копіювати цей код у своєму оточенні і перевіряти, як бажає ваше серце
додано Автор Psilon, джерело
це дивно ... ви не змінили N? Збільшення N збільшення продуктивності дженериків ...
додано Автор Psilon, джерело
спасибо, я додав ваш код в пост, відрізняється виходом з-за платформ x64/x86
додано Автор Dmitry, джерело
на моєму ноутбуці результат: не-загальний метод - 13.9610133964644 разів швидше, або 1296.10133964644%
додано Автор Dmitry, джерело
did'nt змінити N, але я використовую x64. Цікавий на x86 результат генетичний/не той же, але в 6 разів більший
додано Автор Dmitry, джерело

Я думаю, що це відповідь на моє запитання, але яку магію я можу використати для заборони боксу?

Якщо вашою метою є лише порівняння, можна це зробити:

    public class A : IEquatable {
        public bool Equals( A other ) { return this == other; }
    }
    static bool F( IEquatable a, IEquatable b ) where T : IEquatable { return a==b; } 

Це дозволить уникнути боксу.

Що стосується основних відхилень від часу, я думаю, що всі вже встановили, що існує проблема з тим, як ви налаштуєте секундомір. Я використовую іншу техніку, де, якщо я хочу видалити сам цикл з результату часу, я беру порожню базову лінію і просто віднімаю її від різниці часу. Це не ідеально, але воно дає справедливий результат і не уповільнює початок і зупинку таймера знову і знову.

1
додано
var chat = new Chat();
var chat = new Chat();
642 учасників

Обсуждение вопросов по C# / .NET / .NET Core / .NET Standard / Azure Сообщества-организаторы: — @itkpi — @dncuug