Тернарный оператор в два раза медленнее, чем блок if-else?


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- elsechunk и раскомментирую строку троичного оператора, это займет около 157 мс.

Почему это происходит?


96
Первое, что нужно исправить: не используйте DateTimeдля измерения производительности. Использование Stopwatch. Далее, время довольно длинное - это очень короткое время для измерения.
Джон Скит

49
Используйте семя при создании Randomобъекта, чтобы он всегда давал одинаковую последовательность. Если вы тестируете другой код с разными данными, вы очень хорошо видите разницу в производительности.
Гуффа

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

7
@LarryOBrien: Интересный дубль. Я только что провел быстрый тест LINQPad и получил очень разные результаты с отсортированным массивом или нет. Фактически, с этим отсортированным я воспроизводлю ту же самую разницу скорости, о которой сообщают. Удаление сортировки также удаляет разницу во времени.
Крис Синклер

39
Дело в том, что тестирование производительности микрооптимизации сложно . Практически все, что вы наблюдаете в своих результатах, связано с ошибками в тестируемом коде, а не с различиями в содержательном коде. Когда вы исправите те, что перечислены здесь, будет больше, я вас уверяю. Мораль этой истории: не беспокойтесь о микрооптимизациях и не пытайтесь их протестировать. Если код на самом деле трудно измерить, значит, он недостаточно медленный, чтобы быть узким местом; игнорируй это.
Обслуживание

Ответы:


376

Чтобы ответить на этот вопрос, мы рассмотрим код сборки, созданный JIT X86 и X64 для каждого из этих случаев.

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. value64-разрядное целое число, для которого требуется 2 машинные инструкции на X86 (с addпоследующим adc), но только 1 на X64 ( add).

Второе: почему троичный оператор работает медленнее как на X86, так и на X64?

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

JIT X86, с другой стороны, подвергается воздействию в большей степени, потому что добавление нового промежуточного значения во внутренний цикл заставляет его «пролить» другое значение, что приводит как минимум к 2 дополнительным доступам к памяти во внутреннем цикле (см. Доступы чтобы [ebp-14h]в коде тройного X86).


18
Компилятор может также расширить троичный в if-else.
dezfowler

13
Обратите внимание, что x86 медленнее только при использовании ternary - он так же быстр, как x64 при использовании if / else . Итак, вопрос, на который нужно ответить: «почему код X86 намного медленнее, чем X64 при использовании троичного оператора?».
Эрен Эрсонмез

18
Конечно, нет веской причины для этого, и MS должен «исправить» это - ведь Ternary - это просто более короткий синтаксис для if / else ?! Вы, конечно, не ожидали бы платить штраф за производительность в любом случае.
niico

6
@niico нет ничего, что можно исправить в троичном операторе. его использование в этом случае просто вызывает другое распределение регистров. В другом случае это может быть быстрее, чем if / else, как я пытался объяснить в своем ответе.
Eren Ersönmez

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

63

РЕДАКТИРОВАТЬ: Все изменения ... см. Ниже.

Я не могу воспроизвести ваши результаты на x64 CLR, но я могу на 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. запустил ваш код, так же, как вы объяснили. 19 против 52 в x86 и 19 против 21 в x64.
Эрен Эрсонмез,

5
@ user1032613: Теперь я могу воспроизвести ваши результаты. Смотрите мое редактирование. Извиняюсь за то, что сомневаюсь в вас раньше - это удивительно, как может измениться архитектура ...
Джон Скит

3
@ BЈовић: Действительно. Это началось как неспособность воспроизвести это вообще, но развилось в течение долгого времени. Это не дает причины, но я подумал, что это все еще полезная информация (например, разница между x64 и x86), поэтому я оставил ее.
Джон Скит

43

Разница на самом деле не имеет ничего общего с if / else против троичного.

Глядя на сопряженные разборки (здесь я не буду переваривать, пожалуйста, смотрите ответ @ 280Z28), выясняется, что вы сравниваете яблоки и апельсины . В одном случае вы создаете две разные +=операции с постоянными значениями, и то, что вы выбираете, зависит от условия, а в другом случае вы создаете, +=где добавляемое значение зависит от условия.

Если вы хотите действительно сравнить if / else против ternary, это будет более справедливое сравнение (теперь оба будут одинаково «медленными», или мы могли бы даже сказать, что ternary немного быстрее):

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

против

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, но троичная все еще намного медленнее - совсем не то, что вы сказали. Вы делали эксперимент, прежде чем опубликовать этот «ответ»?
user1032613

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 короче.

Обратите внимание, что для разных инструкций [ ИПЦ ] не обязательно одинаков. Логично, что для идентичной инструкции больше инструкций обходится дольше в цикл. Но если время выборки команд и канал / кэш также были приняты во внимание, то реальное общее время выполнения зависит от процессора. Процессор также может прогнозировать ответвления.

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

Я не знаю, был ли аппаратно-реализованный CLR, но если да, вы, вероятно, быстрее справитесь с условным оператором, потому что IL явно меньше.

Примечание: весь машинный код x86.


7

Я сделал то, что сделал Джон Скит, выполнил 1 итерацию и 1000 итераций и получил отличные результаты от OP и Jon. У меня троичный чуть быстрее. Ниже приведен точный код:

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

Истекшее время для троичного: 00: 00: 00.0136723

Истекшее время для If-Else: 00: 00: 14.0167870

Прошедшее время для троичной: 00: 00: 13.9418520

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

Истекшее время для If-Else: 20

Прошедшее время для троичных: 19

Истекшее время для If-Else: 13854

Прошедшее время для тройной: 13610

Это работает в 64-битной XP, и я работал без отладки.

Редактировать - Запуск в x86:

Существует большая разница с использованием x86. Это было сделано без отладки на той же 64-разрядной машине xp, что и раньше, но созданной для процессоров x86. Это больше похоже на ОП.

Истекшее время для If-Else: 18

Прошедшее время для троичных: 35

Истекшее время для If-Else: 20512

Прошедшее время для тройной: 32673


Не могли бы вы попробовать это на x86? Спасибо.
user1032613

@ user1032613 Я думаю, что может быть большая разница, если вы работаете без отладки и отладки.
CodeCamper

@ user1032613 Я только что отредактировал свой пост с данными из x86. Это больше похоже на ваше, где троичный в 2 раза медленнее.
Shaz

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, вы получите тот же код, что и if / 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:

Который такой же как if / else.


4

Запуск без отладки Ctrl + F5, кажется, что отладчик значительно замедляет как if, так и троичный код, но, похоже, значительно замедляет троичный оператор.

Когда я запускаю следующий код, вот мои результаты. Я думаю, что небольшое различие в миллисекундах вызвано тем, что компилятор оптимизирует 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 (ветвь, если true), чтобы перепрыгнуть через первое условие, и безусловную ветвь, чтобы перейти от первого из оператора if. Условный код использует bgt (ветвь, если больше, чем) вместо brtrue, что может быть более медленным сравнением.

Также (только что прочитав о предсказании ветвления) может быть снижение производительности для ветви, которая меньше. Условная ветвь имеет только 1 инструкцию внутри ветви, но if / else имеет 7. Это также объясняет, почему существует разница между использованием long и int, потому что переход на int уменьшает количество инструкций в ветвях if / else на 1 (делая упреждающее чтение меньше)


1

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

If / Else: 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;

В одном из приведенных ниже ответов также отметим, что плохой способ написания троичного оператора.

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

Вложенный троичный оператор: я нашел вложенный троичный оператор и несколько, если блок else также будет выполняться одновременно.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.