Термінальний оператор вдвічі повільніше, ніж блок "інше"?


246

Я читаю всюди, що потрійний оператор повинен бути швидшим, або, принаймні, таким же, як його еквівалент if- elseблок.

Однак я зробив наступний тест і виявив, що це не так:

Random r = new Random();
int[] array = new int[20000000];
for(int i = 0; i < array.Length; i++)
{
    array[i] = r.Next(int.MinValue, int.MaxValue);
}
Array.Sort(array);

long value = 0;
DateTime begin = DateTime.UtcNow;

foreach (int i in array)
{
    if (i > 0)
    {
        value += 2;
    }
    else
    {
        value += 3;
    }
    // if-else block above takes on average 85 ms

    // OR I can use a ternary operator:
    // value += i > 0 ? 2 : 3; // takes 157 ms
}
DateTime end = DateTime.UtcNow;
MessageBox.Show("Measured time: " + (end-begin).TotalMilliseconds + " ms.\r\nResult = " + value.ToString());

Мій комп'ютер запустив код вище 85 мс. Але якщо я прокоментую if- elseшматок і відменюю потрійну лінію оператора, це займе близько 157 мс.

Чому це відбувається?


96
Перше, що потрібно виправити: не використовуйте DateTimeдля вимірювання продуктивності. Використовуйте Stopwatch. Далі, час довше - це дуже короткий час для вимірювання.
Джон Скіт

49
Використовуйте насіння, коли ви створюєте Randomоб'єкт, щоб він завжди давав однакову послідовність. Якщо ви протестуєте різний код з різними даними, ви можете дуже добре бачити відмінності в продуктивності.
Guffa

12
Ви також намагалися компілювати / запускати його у режимі випуску із увімкненою оптимізацією компілятора та без додавання налагоджувача?
Кріс Сінклер

7
@LarryOBrien: Цікаво взяти. Я щойно зробив швидкий тест LINQPad і отримав дуже різні результати за сортуванням масиву чи ні. Насправді, за допомогою сортування я відтворюю ту саму різницю швидкості, про яку повідомлялося. Видалення сортування також усуває різницю у часі.
Кріс Сінклер

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

Відповіді:


376

Щоб відповісти на це запитання, ми вивчимо код складання, створений X86 та X64 JIT для кожного з цих випадків.

X86, якщо / то

    32:                 foreach (int i in array)
0000007c 33 D2                xor         edx,edx 
0000007e 83 7E 04 00          cmp         dword ptr [esi+4],0 
00000082 7E 1C                jle         000000A0 
00000084 8B 44 96 08          mov         eax,dword ptr [esi+edx*4+8] 
    33:                 {
    34:                     if (i > 0)
00000088 85 C0                test        eax,eax 
0000008a 7E 08                jle         00000094 
    35:                     {
    36:                         value += 2;
0000008c 83 C3 02             add         ebx,2 
0000008f 83 D7 00             adc         edi,0 
00000092 EB 06                jmp         0000009A 
    37:                     }
    38:                     else
    39:                     {
    40:                         value += 3;
00000094 83 C3 03             add         ebx,3 
00000097 83 D7 00             adc         edi,0 
0000009a 42                   inc         edx 
    32:                 foreach (int i in array)
0000009b 39 56 04             cmp         dword ptr [esi+4],edx 
0000009e 7F E4                jg          00000084 
    30:             for (int x = 0; x < iterations; x++)
000000a0 41                   inc         ecx 
000000a1 3B 4D F0             cmp         ecx,dword ptr [ebp-10h] 
000000a4 7C D6                jl          0000007C 

X86, потрійний

    59:                 foreach (int i in array)
00000075 33 F6                xor         esi,esi 
00000077 83 7F 04 00          cmp         dword ptr [edi+4],0 
0000007b 7E 2D                jle         000000AA 
0000007d 8B 44 B7 08          mov         eax,dword ptr [edi+esi*4+8] 
    60:                 {
    61:                     value += i > 0 ? 2 : 3;
00000081 85 C0                test        eax,eax 
00000083 7F 07                jg          0000008C 
00000085 BA 03 00 00 00       mov         edx,3 
0000008a EB 05                jmp         00000091 
0000008c BA 02 00 00 00       mov         edx,2 
00000091 8B C3                mov         eax,ebx 
00000093 8B 4D EC             mov         ecx,dword ptr [ebp-14h] 
00000096 8B DA                mov         ebx,edx 
00000098 C1 FB 1F             sar         ebx,1Fh 
0000009b 03 C2                add         eax,edx 
0000009d 13 CB                adc         ecx,ebx 
0000009f 89 4D EC             mov         dword ptr [ebp-14h],ecx 
000000a2 8B D8                mov         ebx,eax 
000000a4 46                   inc         esi 
    59:                 foreach (int i in array)
000000a5 39 77 04             cmp         dword ptr [edi+4],esi 
000000a8 7F D3                jg          0000007D 
    57:             for (int x = 0; x < iterations; x++)
000000aa FF 45 E4             inc         dword ptr [ebp-1Ch] 
000000ad 8B 45 E4             mov         eax,dword ptr [ebp-1Ch] 
000000b0 3B 45 F0             cmp         eax,dword ptr [ebp-10h] 
000000b3 7C C0                jl          00000075 

X64, якщо / то

    32:                 foreach (int i in array)
00000059 4C 8B 4F 08          mov         r9,qword ptr [rdi+8] 
0000005d 0F 1F 00             nop         dword ptr [rax] 
00000060 45 85 C9             test        r9d,r9d 
00000063 7E 2B                jle         0000000000000090 
00000065 33 D2                xor         edx,edx 
00000067 45 33 C0             xor         r8d,r8d 
0000006a 4C 8B 57 08          mov         r10,qword ptr [rdi+8] 
0000006e 66 90                xchg        ax,ax 
00000070 42 8B 44 07 10       mov         eax,dword ptr [rdi+r8+10h] 
    33:                 {
    34:                     if (i > 0)
00000075 85 C0                test        eax,eax 
00000077 7E 07                jle         0000000000000080 
    35:                     {
    36:                         value += 2;
00000079 48 83 C5 02          add         rbp,2 
0000007d EB 05                jmp         0000000000000084 
0000007f 90                   nop 
    37:                     }
    38:                     else
    39:                     {
    40:                         value += 3;
00000080 48 83 C5 03          add         rbp,3 
00000084 FF C2                inc         edx 
00000086 49 83 C0 04          add         r8,4 
    32:                 foreach (int i in array)
0000008a 41 3B D2             cmp         edx,r10d 
0000008d 7C E1                jl          0000000000000070 
0000008f 90                   nop 
    30:             for (int x = 0; x < iterations; x++)
00000090 FF C1                inc         ecx 
00000092 41 3B CC             cmp         ecx,r12d 
00000095 7C C9                jl          0000000000000060 

X64, потрійний

    59:                 foreach (int i in array)
00000044 4C 8B 4F 08          mov         r9,qword ptr [rdi+8] 
00000048 45 85 C9             test        r9d,r9d 
0000004b 7E 2F                jle         000000000000007C 
0000004d 45 33 C0             xor         r8d,r8d 
00000050 33 D2                xor         edx,edx 
00000052 4C 8B 57 08          mov         r10,qword ptr [rdi+8] 
00000056 8B 44 17 10          mov         eax,dword ptr [rdi+rdx+10h] 
    60:                 {
    61:                     value += i > 0 ? 2 : 3;
0000005a 85 C0                test        eax,eax 
0000005c 7F 07                jg          0000000000000065 
0000005e B8 03 00 00 00       mov         eax,3 
00000063 EB 05                jmp         000000000000006A 
00000065 B8 02 00 00 00       mov         eax,2 
0000006a 48 63 C0             movsxd      rax,eax 
0000006d 4C 03 E0             add         r12,rax 
00000070 41 FF C0             inc         r8d 
00000073 48 83 C2 04          add         rdx,4 
    59:                 foreach (int i in array)
00000077 45 3B C2             cmp         r8d,r10d 
0000007a 7C DA                jl          0000000000000056 
    57:             for (int x = 0; x < iterations; x++)
0000007c FF C1                inc         ecx 
0000007e 3B CD                cmp         ecx,ebp 
00000080 7C C6                jl          0000000000000048 

По-перше: чому код X86 набагато повільніше, ніж X64?

Це пов'язано з такими характеристиками коду:

  1. У X64 є кілька додаткових регістрів, і кожен регістр має 64 біти. Це дозволяє X64 JIT повністю виконувати внутрішній цикл, використовуючи регістри, окрім завантаження iз масиву, тоді як X86 JIT розміщує в циклі кілька операцій стека (доступ до пам'яті).
  2. valueце 64-розрядне ціле число, яке вимагає 2 машинних інструкцій на X86 ( addдалі adc), але лише 1 на X64 ( add).

По-друге: чому потрійний оператор повільніше і на X86, і на X64?

Це пов'язано з тонкою різницею в порядку операцій, що впливають на оптимізатор JIT. Для JIT потрійний оператор, а не безпосередньо кодування 2та 3в самих addмашинних інструкціях, JIT створює проміжну змінну (в регістрі) для утримання результату. Потім цей регістр розширюється знаком з 32-бітного до 64-бітного перед тим, як додати його до value. Оскільки все це виконується в регістрах для X64, незважаючи на значне збільшення складності для потрійного оператора, чистий вплив дещо мінімізується.

З іншого боку, на X86 JIT впливає більша ступінь, оскільки додавання нового проміжного значення у внутрішній цикл призводить до того, що він "проллє" інше значення, в результаті чого принаймні два додаткові доступу до пам'яті у внутрішньому циклі (див. Доступ до [ebp-14h]потрійного коду X86).


18
Компілятор може також розширити тернар на інше.
dezfowler

13
Зауважте, що x86 є лише повільнішим при використанні терміналу - він так само швидкий, як і x64 при використанні if / else . Тож питання, на яке слід відповісти: "чому код X86 настільки повільніше, ніж X64, коли використовується потрійний оператор?".
Eren Ersönmez

18
Звичайно, для цього немає вагомих причин, і МС має це "виправити" - оскільки Тернарі фактично є лише більш коротким синтаксисом для if / else ?! Ви, звичайно, не сподівались заплатити штрафний показник за виконання.
niico

6
@niico нічого не може «виправити» щодо потрійного оператора. його використання в цьому випадку просто відбувається, щоб викликати інше розподілення реєстру. В іншому випадку це може бути швидше, ніж якщо / ще, як я намагався пояснити у своїй відповіді.
Eren Ersönmez

6
@ ErenErsönmez: Впевнений, що є щось виправити. Команда оптимізатора може ретельно проаналізувати два випадки і знайти спосіб зробити так, щоб потрійний оператор в цьому випадку був таким же швидким, як інакше. Звичайно, таке виправлення може виявитися нездійсненним або занадто дорогим.
Брайан

63

EDIT: Усі зміни ... див. Нижче.

Я не можу відтворити ваші результати на CL64 x64, але можу на x86. На x64 я бачу невелику різницю (менше 10%) між умовним оператором і if / else, але це набагато менше, ніж ви бачите.

Я вніс такі можливі зміни:

  • Запуск у консольному додатку
  • Створіть /o+ /debug-і виконайте виправлення поза налагоджувачем
  • Запустіть обидва шматки коду один раз до JIT, потім багато разів для більшої точності
  • Використовуйте Stopwatch

Результати /platform:x64(без рядків "ігнорувати"):

if/else with 1 iterations: 17ms
conditional with 1 iterations: 19ms
if/else with 1000 iterations: 17875ms
conditional with 1000 iterations: 19089ms

Результати /platform:x86(без рядків "ігнорувати"):

if/else with 1 iterations: 18ms
conditional with 1 iterations: 49ms
if/else with 1000 iterations: 17901ms
conditional with 1000 iterations: 47710ms

Мої дані системи:

  • x64 i7-2720QM CPU при 2,20 ГГц
  • 64-розрядна Windows 8
  • .NET 4.5

Так що в відміну від раніше, я думаю , що ви будете бачити реальну різницю - і все це робити з x86 JIT. Я не хотів би сказати саме те , що спричиняє різницю - я можу оновити публікацію пізніше, щоб отримати докладніші відомості, якщо я можу потурбуватися зайти в cordbg :)

Цікаво, що, не сортуючи масив спочатку, я закінчую тести, які займають приблизно 4,5 рази довше, принаймні на x64. Я здогадуюсь, що це стосується прогнозування галузей.

Код:

using System;
using System.Diagnostics;

class Test
{
    static void Main()
    {
        Random r = new Random(0);
        int[] array = new int[20000000];
        for(int i = 0; i < array.Length; i++)
        {
            array[i] = r.Next(int.MinValue, int.MaxValue);
        }
        Array.Sort(array);
        // JIT everything...
        RunIfElse(array, 1);
        RunConditional(array, 1);
        // Now really time it
        RunIfElse(array, 1000);
        RunConditional(array, 1000);
    }

    static void RunIfElse(int[] array, int iterations)
    {        
        long value = 0;
        Stopwatch sw = Stopwatch.StartNew();

        for (int x = 0; x < iterations; x++)
        {
            foreach (int i in array)
            {
                if (i > 0)
                {
                    value += 2;
                }
                else
                {
                    value += 3;
                }
            }
        }
        sw.Stop();
        Console.WriteLine("if/else with {0} iterations: {1}ms",
                          iterations,
                          sw.ElapsedMilliseconds);
        // Just to avoid optimizing everything away
        Console.WriteLine("Value (ignore): {0}", value);
    }

    static void RunConditional(int[] array, int iterations)
    {        
        long value = 0;
        Stopwatch sw = Stopwatch.StartNew();

        for (int x = 0; x < iterations; x++)
        {
            foreach (int i in array)
            {
                value += i > 0 ? 2 : 3;
            }
        }
        sw.Stop();
        Console.WriteLine("conditional with {0} iterations: {1}ms",
                          iterations,
                          sw.ElapsedMilliseconds);
        // Just to avoid optimizing everything away
        Console.WriteLine("Value (ignore): {0}", value);
    }
}

31
Тож питання, до якого все ще вмирають знати, це чому є навіть крихітна різниця.
Бред М

1
@BradM: Ну, IL буде іншим, і будь-яка різниця взагалі могла б робити всілякі речі до того моменту, як це буде зібрано JIT, а потім сам процесор зробив це противно.
Джон Скіт

4
@JonSkeet FYI. запустив свій код, точно так, як ви пояснили. 19s проти 52s у x86, та 19s проти 21s у x64.
Eren Ersönmez

5
@ user1032613: Тепер я можу відтворити ваші результати. Дивіться мою редакцію. Вибачте за те, що раніше сумніваєтесь у вас - дивовижна різниця, яку може змінити архітектура ...
Джон Скіт

3
@ Бйович: Дійсно. Це почалося так, що його взагалі не можна було відтворити, але розвивалося з часом. Це не дає причини, але я подумав, що це все-таки корисна інформація (наприклад, різниця x64 проти x86), тому я залишив її.
Джон Скіт

43

Різниця насправді не має великого відношення до if / else vs ternary.

Дивлячись на розрізнені розбірки (я не буду переставляти тут, будь ласка, дивіться відповідь @ 280Z28), виявляється, ви порівнюєте яблука та апельсини . В одному випадку ви створюєте дві різні +=операції з постійними значеннями, і яку ви виберете, залежить від умови, а в іншому випадку ви створюєте a, +=де значення для додавання залежить від умови.

Якщо ви хочете по-справжньому порівняти if / else vs ternary, це було б більш справедливим порівнянням (тепер обидва будуть однаково «повільними», або ми могли б навіть сказати, що потрійний трохи швидший):

int diff;
if (i > 0) 
    diff = 2;
else 
    diff = 3;
value += diff;

vs.

value += i > 0 ? 2 : 3;

Тепер розбирання для if/elseстане, як показано нижче. Зауважте, що це трохи гірше, ніж потрійний випадок, оскільки він вийшов із використання регістрів для змінної циклу ( i).

                if (i > 0)
0000009d  cmp         dword ptr [ebp-20h],0 
000000a1  jle         000000AD 
                {
                    diff = 2;
000000a3  mov         dword ptr [ebp-24h],2 
000000aa  nop 
000000ab  jmp         000000B4 
                }
                else
                {
                    diff = 3;
000000ad  mov         dword ptr [ebp-24h],3 
                }
                value += diff;
000000b4  mov         eax,dword ptr [ebp-18h] 
000000b7  mov         edx,dword ptr [ebp-14h] 
000000ba  mov         ecx,dword ptr [ebp-24h] 
000000bd  mov         ebx,ecx 
000000bf  sar         ebx,1Fh 
000000c2  add         eax,ecx 
000000c4  adc         edx,ebx 
000000c6  mov         dword ptr [ebp-18h],eax 
000000c9  mov         dword ptr [ebp-14h],edx 
000000cc  inc         dword ptr [ebp-28h] 

5
Як щодо підкреслення порівняння яблук та апельсинів ?
Кен Кін

6
Ну, я б насправді не сказав, що це порівняння яблук і апельсинів. Обидва варіанти мають однакову семантику , тому оптимізатор може спробувати обидва варіанти оптимізації та вибрати той, який є ефективнішим в будь-якому випадку.
Влад

Я зробив тест, як ви запропонували: ввів ще одну змінну diff, але потрійний все ще набагато повільніше - зовсім не те, що ви сказали. Ви зробили експеримент перед тим, як опублікувати цю "відповідь"?
користувач1032613

9

Редагувати:

Додано приклад, який можна зробити за допомогою оператора if-else, але не умовного оператора.


Перш ніж відповісти, будь ласка, подивіться [ Що швидше? ] у блозі містера Ліпперта І я вважаю , що відповідь пана Ерсьонмеса тут є найбільш правильною.

Я намагаюся зазначити щось, про що слід пам’ятати, використовуючи мову програмування високого рівня.

По-перше, я ніколи не чув, щоб умовний оператор мав бути швидшим або однаковою продуктивністю з твердженням if-else у C♯ .

Причина проста в тому, що, якщо немає операції з оператором if-else:

if (i > 0)
{
    value += 2;
}
else
{
}

Вимога умовного оператора полягає в тому, що має бути значення з будь-якої сторони, і в C♯ воно також вимагає, щоб обидві сторони :мали один і той же тип. Це просто відрізняє від заяви if-else. Таким чином, ваше питання перетворюється на запитання про те, як формується інструкція машинного коду, щоб різниця в продуктивності.

З умовним оператором семантично це:

Який би вираз не оцінювався, є значення.

Але з твердженням if-else:

Якщо вираз оцінено як істинне, зробіть щось; якщо ні, то зробіть іншу справу.

Значення не обов'язково пов'язане з оператором if-else. Ваше припущення можливе лише за допомогою оптимізації.

Ще один приклад продемонструвати різницю між ними:

var array1=new[] { 1, 2, 3 };
var array2=new[] { 5, 6, 7 };

if(i>0)
    array1[1]=4;
else
    array2[2]=4;

код вище компілюється, однак, замінити оператор if-else на умовний оператор просто не складеться:

var array1=new[] { 1, 2, 3 };
var array2=new[] { 5, 6, 7 };
(i>0?array1[1]:array2[2])=4; // incorrect usage 

Умовний оператор і оператори if-else є концептуальними однаковими, коли ви робите те саме, що, можливо, ще швидше з умовним оператором в C , оскільки C більше наближений до складання платформи.


Для оригінального коду, який ви надали, умовний оператор використовується в циклі foreach, який би зіпсував речі, щоб побачити різницю між ними. Тому я пропоную наступний код:

public static class TestClass {
    public static void TestConditionalOperator(int i) {
        long value=0;
        value+=i>0?2:3;
    }

    public static void TestIfElse(int i) {
        long value=0;

        if(i>0) {
            value+=2;
        }
        else {
            value+=3;
        }
    }

    public static void TestMethod() {
        TestConditionalOperator(0);
        TestIfElse(0);
    }
}

і нижче наведені дві версії ІЛ оптимізовані і не. Оскільки вони довгі, я використовую зображення, щоб показати, права частина оптимізована:

(Клацніть для перегляду повнорозмірного зображення.) hSN6s.png

В обох версіях коду IL умовного оператора виглядає коротше, ніж оператор if-else, і все ще залишається сумнів у остаточному створенні машинного коду. Нижче наведено інструкції обох методів, і колишнє зображення не оптимізоване, а останнє - оптимізоване:

  • Неоптимізовані інструкції: (Клацніть, щоб побачити зображення в повному розмірі.) ybhgM.png

  • Оптимізовані інструкції: (Клацніть для перегляду повнорозмірного зображення.) 6kgzJ.png

В останньому жовтий блок - це код, який виконується лише якщо i<=0, а синій - коли i>0. В будь-якій версії інструкцій if-else оператор коротший.

Зауважте, що для різних інструкцій [ CPI ] необов’язково однаковий. Логічно, для однакової інструкції більше інструкцій коштує довший цикл. Але якщо час отримання інструкцій та кеш / кеш також враховувались, то реальний загальний час виконання залежить від процесора. Процесор також може передбачити гілки.

Сучасні процесори мають ще більше ядер, з цим все може бути складніше. Якщо ви були користувачем процесорів Intel, можливо, ви захочете ознайомитись з посібником з оптимізації архітектури Intel® 64 та IA-32 ].

Я не знаю, чи існував апаратно реалізований CLR, але якщо так, то, ймовірно, ви швидше ставитеся до умовного оператора, оскільки ІЛ явно менше.

Примітка: Усі машинні коди мають x86.


7

Я зробив те, що зробив Джон Скіт, і пробіг 1 ітерацію та 1000 ітерацій і отримав різний результат як від ОП, так і від Йона. У моєму, трійця просто трохи швидша. Нижче наведено точний код:

static void runIfElse(int[] array, int iterations)
    {
        long value = 0;
        Stopwatch ifElse = new Stopwatch();
        ifElse.Start();
        for (int c = 0; c < iterations; c++)
        {
            foreach (int i in array)
            {
                if (i > 0)
                {
                    value += 2;
                }
                else
                {
                    value += 3;
                }
            }
        }
        ifElse.Stop();
        Console.WriteLine(String.Format("Elapsed time for If-Else: {0}", ifElse.Elapsed));
    }

    static void runTernary(int[] array, int iterations)
    {
        long value = 0;
        Stopwatch ternary = new Stopwatch();
        ternary.Start();
        for (int c = 0; c < iterations; c++)
        {
            foreach (int i in array)
            {
                value += i > 0 ? 2 : 3;
            }
        }
        ternary.Stop();


        Console.WriteLine(String.Format("Elapsed time for Ternary: {0}", ternary.Elapsed));
    }

    static void Main(string[] args)
    {
        Random r = new Random();
        int[] array = new int[20000000];
        for (int i = 0; i < array.Length; i++)
        {
            array[i] = r.Next(int.MinValue, int.MaxValue);
        }
        Array.Sort(array);

        long value = 0;

        runIfElse(array, 1);
        runTernary(array, 1);
        runIfElse(array, 1000);
        runTernary(array, 1000);
        
        Console.ReadLine();
    }

Вихід з моєї програми:

Час минулого для If-Else: 00: 00: 00.0140543

Час, що минув для Ternary: 00: 00: 00.0136723

Час минулого для If-Else: 00: 00: 14.0167870

Час минулого для Тернарі: 00: 00: 13,9418520

Ще один пробіг в мілісекундах:

Час минулого для If-Else: 20

Час, що минув для Тернарі: 19

Час, що минув для If-Else: 13854

Час, що минув для Ternary: 13610

Це працює в 64-бітному XP, і я працював без налагодження.

Редагувати - працює в x86:

Існує велика різниця за допомогою x86. Це було зроблено без налагодження на тому самому 64-бітному комп'ютері xp, що і раніше, але побудований для процесорів x86. Це більше схоже на ОП.

Час, що минув для If-Else: 18

Час, що минув для Тернарі: 35

Час минулого для If-Else: 20512

Час, що минув для Ternary: 32673


Не могли б ви спробувати на x86? Дякую.
користувач1032613

@ user1032613 Я думаю, що може бути велика різниця, якщо ви працюєте без налагодження vs з налагодженням.
CodeCamper

@ user1032613 Я щойно редагував свою публікацію з даними x86. Він більше схожий на ваш, де потрійний у 2 рази повільніше.
Шаз

5

Створений код асемблера розповість історію:

a = (b > c) ? 1 : 0;

Створює:

mov  edx, DWORD PTR a[rip]
mov  eax, DWORD PTR b[rip]
cmp  edx, eax
setg al

При цьому:

if (a > b) printf("a");
else printf("b");

Створює:

mov edx, DWORD PTR a[rip]
mov eax, DWORD PTR b[rip]
cmp edx, eax
jle .L4
    ;printf a
jmp .L5
.L4:
    ;printf b
.L5:

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

a = (b > c) ? 2 : 3;

Створює:

mov edx, DWORD PTR b[rip]
mov eax, DWORD PTR c[rip]
cmp edx, eax
jle .L6
    mov eax, 2
jmp .L7
.L6:
    mov eax, 3
.L7:

Що таке саме, як якщо / ще.


4

Запустити без налагодження ctrl + F5, здається, що налагоджувач значно сповільнює і ifs, і ternary, але, здається, він значно сповільнює потрійний оператор.

Коли я запускаю наступний код, ось мої результати. Я думаю, що невелика різниця в мілісекундах викликана компілятором, який оптимізує max = max та видаляє його, але, ймовірно, не робить такої оптимізації для потрійного оператора. Якби хтось міг перевірити збірку і підтвердити це, було б дивним.

--Run #1--
Type   | Milliseconds
Ternary 706
If     704
%: .9972
--Run #2--
Type   | Milliseconds
Ternary 707
If     704
%: .9958
--Run #3--
Type   | Milliseconds
Ternary 706
If     704
%: .9972

Код

  for (int t = 1; t != 10; t++)
        {
            var s = new System.Diagnostics.Stopwatch();
            var r = new Random(123456789);   //r
            int[] randomSet = new int[1000]; //a
            for (int i = 0; i < 1000; i++)   //n
                randomSet[i] = r.Next();     //dom
            long _ternary = 0; //store
            long _if = 0;      //time
            int max = 0; //result
            s.Start();
            for (int q = 0; q < 1000000; q++)
            {
                for (int i = 0; i < 1000; i++)
                    max = max > randomSet[i] ? max : randomSet[i];
            }
            s.Stop();
            _ternary = s.ElapsedMilliseconds;
            max = 0;
            s = new System.Diagnostics.Stopwatch();
            s.Start();
            for (int q = 0; q < 1000000; q++)
            {
                for (int i = 0; i < 1000; i++)
                    if (max > randomSet[i])
                        max = max; // I think the compiler may remove this but not for the ternary causing the speed difference.
                    else
                        max = randomSet[i];
            }

            s.Stop();
            _if = s.ElapsedMilliseconds;
            Console.WriteLine("--Run #" + t+"--");
            Console.WriteLine("Type   | Milliseconds\nTernary {0}\nIf     {1}\n%: {2}", _ternary, _if,((decimal)_if/(decimal)_ternary).ToString("#.####"));
        }

4

Дивлячись на генерований IL, на це 16 операцій, ніж у операторі if / else (копіювання та вставка коду @ JonSkeet). Однак це не означає, що це повинен бути швидший процес!

Підсумовуючи відмінності в IL, метод if / else перекладається приблизно так само, як читає код C # (виконуючи додавання у гілці), тоді як умовний код завантажує або 2, або 3 на стек (залежно від значення) та потім додає його до значення поза умовного.

Інша відмінність - це інструкція з розгалуження, що використовується. Метод if / else використовує brtrue (гілку, якщо це правда), щоб перейти через першу умову, а безумовну гілку - перейти з першого з оператора if. Умовний код використовує bgt (гілка, якщо більша, ніж) замість brtrue, що, можливо, може бути повільнішим порівнянням.

Крім того (щойно прочитавши про прогнозування галузей), може бути штрафний показник продуктивності, якщо галузь буде меншою. У умовній гілці є лише 1 інструкція у межах гілки, але if / else має 7. Це також пояснить, чому існує різниця між використанням long та int, оскільки зміна на int зменшує кількість інструкцій у гілках if / else на 1 (роблячи читання вперед менше)


1

У наступному коді if / else здається приблизно в 1,4 рази швидшим, ніж потрійний оператор. Однак я виявив, що введення тимчасової змінної зменшує час запуску потрійного оператора приблизно в 1,4 рази:

Якщо / інше: 98 мс

Термінал: 141 мс

Термінал з тимчасовою вар.: 100 мс

using System;
using System.Diagnostics;

namespace ConsoleApplicationTestIfElseVsTernaryOperator
{
    class Program
    {
        static void Main(string[] args)
        {
            Random r = new Random(0);
            int[] array = new int[20000000];
            for (int i = 0; i < array.Length; i++)
            {
                array[i] = r.Next(int.MinValue, int.MaxValue);
            }
            Array.Sort(array);
            long value;
            Stopwatch stopwatch = new Stopwatch();

            value = 0;
            stopwatch.Restart();
            foreach (int i in array)
            {
                if (i > 0)
                {
                    value += 2;
                }
                else
                {
                    value += 3;
                }
                // 98 ms
            }
            stopwatch.Stop();
            Console.WriteLine("If/Else: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

            value = 0;
            stopwatch.Restart();
            foreach (int i in array)
            {
                value += (i > 0) ? 2 : 3; 
                // 141 ms
            }

            stopwatch.Stop();
            Console.WriteLine("Ternary: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

            value = 0;
            int tempVar = 0;
            stopwatch.Restart();
            foreach (int i in array)
            {
                tempVar = (i > 0) ? 2 : 3;
                value += tempVar; 
                // 100ms
            }
            stopwatch.Stop();
            Console.WriteLine("Ternary with temp var: " + stopwatch.ElapsedMilliseconds.ToString() + " ms");

            Console.ReadKey(true);
        }
    }
}

0

Занадто багато чудових відповідей, але я знайшов щось цікаве, дуже прості зміни впливають. Після внесення змін нижче, для виконання if-else та термінального оператора знадобиться той самий час.

замість того, щоб писати під рядком

value +=  i > 0 ? 2 : 3;

Я використав це,

int a =  i > 0 ? 2 : 3;
value += a;

Один із наведених нижче відповідей також зазначає, що поганий спосіб написання потрійного оператора.

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

Вкладений термінальний оператор: Я знайшов вкладений потрійний оператор і кілька, якщо інший блок також потребує виконання часу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.