Я читал везде , что тройная оператор должен быть быстрее , чем, или по крайней мере так же , как, его эквивалент 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
chunk и раскомментирую строку троичного оператора, это займет около 157 мс.
Почему это происходит?
c#
performance
conditional-operator
user1032613
источник
источник
DateTime
для измерения производительности. ИспользованиеStopwatch
. Далее, время довольно длинное - это очень короткое время для измерения.Random
объекта, чтобы он всегда давал одинаковую последовательность. Если вы тестируете другой код с разными данными, вы очень хорошо видите разницу в производительности.Ответы:
Чтобы ответить на этот вопрос, мы рассмотрим код сборки, созданный JIT X86 и X64 для каждого из этих случаев.
X86, если / тогда
X86, троичный
X64, если / тогда
X64, троичный
Во-первых: почему код X86 намного медленнее, чем X64?
Это связано со следующими характеристиками кода:
i
из массива, тогда как X86 JIT помещает в цикл несколько операций стека (доступ к памяти).value
64-разрядное целое число, для которого требуется 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).источник
РЕДАКТИРОВАТЬ: Все изменения ... см. Ниже.
Я не могу воспроизвести ваши результаты на x64 CLR, но я могу на x86. На x64 я вижу небольшую разницу (менее 10%) между условным оператором и if / else, но она намного меньше, чем вы видите.
Я сделал следующие потенциальные изменения:
/o+ /debug-
и запуск за пределами отладчикаStopwatch
Результаты с
/platform:x64
(без "игнорируемых" строк):Результаты с
/platform:x86
(без "игнорируемых" строк):Детали моей системы:
Так что в отличие от ранее, я думаю , что вы будете видеть реальную разницу - и все это делать с x86 JIT. Я не хотел бы точно сказать, что является причиной разницы - я могу обновить пост позже с более подробной информацией, если я могу потрудиться перейти на cordbg :)
Интересно, что без предварительной сортировки массива я получаю тесты, которые занимают примерно 4,5 раза дольше, по крайней мере на x64. Я предполагаю, что это связано с предсказанием ветвлений.
Код:
источник
Разница на самом деле не имеет ничего общего с if / else против троичного.
Глядя на сопряженные разборки (здесь я не буду переваривать, пожалуйста, смотрите ответ @ 280Z28), выясняется, что вы сравниваете яблоки и апельсины . В одном случае вы создаете две разные
+=
операции с постоянными значениями, и то, что вы выбираете, зависит от условия, а в другом случае вы создаете,+=
где добавляемое значение зависит от условия.Если вы хотите действительно сравнить if / else против ternary, это будет более справедливое сравнение (теперь оба будут одинаково «медленными», или мы могли бы даже сказать, что ternary немного быстрее):
против
Теперь разборка для
if/else
становится, как показано ниже. Обратите внимание, что это немного хуже троичного случая, поскольку он также прекратил использование регистров для переменной цикла (i
).источник
diff
, но троичная все еще намного медленнее - совсем не то, что вы сказали. Вы делали эксперимент, прежде чем опубликовать этот «ответ»?Редактировать:
Добавлен пример, который можно сделать с помощью оператора if-else, но не с условным оператором.
Перед ответом, пожалуйста, посмотрите [ Что быстрее? ] в блоге мистера Липперта. И я думаю, что ответ г-на Эрсонмеза является наиболее правильным здесь.
Я пытаюсь упомянуть кое-что, что мы должны иметь в виду с языком программирования высокого уровня.
Во-первых, я никогда не слышал, что условный оператор должен быть быстрее или с той же производительностью, что и оператор if-else в C♯ .
Причина проста: что если нет операции с оператором if-else:
Требование условного оператора состоит в том, что должно быть значение с любой стороны, а в C♯ также требуется, чтобы обе стороны имели
:
одинаковый тип. Это просто отличает его от оператора if-else. Таким образом, ваш вопрос становится вопросом, спрашивающим, как генерируется инструкция машинного кода, так что разница в производительности.С условным оператором семантически это:
Какое бы выражение ни оценивалось, оно имеет значение.
Но с оператором if-else:
Если выражение оценено как истинное, сделайте что-нибудь; если нет, сделай другое.
Значение не обязательно связано с оператором if-else. Ваше предположение возможно только с оптимизацией.
Другой пример, демонстрирующий разницу между ними:
код выше компилирует, однако, заменить оператор if-else условным оператором просто не скомпилируется:
Условный оператор и оператор if-else являются концептуальными, когда вы делаете одно и то же, возможно, это даже быстрее с условным оператором в C , так как C ближе к сборке платформы.
Для исходного кода, который вы предоставили, условный оператор используется в цикле foreach, который запутал бы вещи, чтобы увидеть разницу между ними. Поэтому я предлагаю следующий код:
и следующие две версии ИЛ оптимизированы и нет. Поскольку они длинные, я использую изображение, чтобы показать, правая часть оптимизирована:
В обеих версиях кода IL условного оператора выглядит короче, чем оператор if-else, и все еще остается сомнение в окончательном генерировании машинного кода. Ниже приведены инструкции для обоих методов, и первое изображение не оптимизировано, а второе оптимизировано:
Неоптимизированные инструкции: (Нажмите, чтобы увидеть полноразмерное изображение.)
Оптимизированные инструкции: (Нажмите, чтобы увидеть полноразмерное изображение.)
В последнем случае желтый блок - это код, который выполняется только тогда
i<=0
, а синий - когдаi>0
. В любой версии инструкций оператор if-else короче.Обратите внимание, что для разных инструкций [ ИПЦ ] не обязательно одинаков. Логично, что для идентичной инструкции больше инструкций обходится дольше в цикл. Но если время выборки команд и канал / кэш также были приняты во внимание, то реальное общее время выполнения зависит от процессора. Процессор также может прогнозировать ответвления.
Современные процессоры имеют еще больше ядер, с этим все может быть сложнее. Если вы были пользователем процессора Intel, вам, возможно, захочется взглянуть на [ Справочное руководство по оптимизации архитектур Intel® 64 и IA-32 ].
Я не знаю, был ли аппаратно-реализованный CLR, но если да, вы, вероятно, быстрее справитесь с условным оператором, потому что IL явно меньше.
Примечание: весь машинный код x86.
источник
Я сделал то, что сделал Джон Скит, выполнил 1 итерацию и 1000 итераций и получил отличные результаты от OP и Jon. У меня троичный чуть быстрее. Ниже приведен точный код:
Выход из моей программы:
Еще один пробег в миллисекундах:
Это работает в 64-битной XP, и я работал без отладки.
Редактировать - Запуск в x86:
Существует большая разница с использованием x86. Это было сделано без отладки на той же 64-разрядной машине xp, что и раньше, но созданной для процессоров x86. Это больше похоже на ОП.
источник
Сгенерированный код ассемблера расскажет историю:
Формирует:
В то время как:
Формирует:
Таким образом, троичный может быть короче и быстрее просто благодаря использованию меньшего количества инструкций и отсутствию скачков, если вы ищете истину / ложь. Если вы используете значения, отличные от 1 и 0, вы получите тот же код, что и if / else, например:
Формирует:
Который такой же как if / else.
источник
Запуск без отладки Ctrl + F5, кажется, что отладчик значительно замедляет как if, так и троичный код, но, похоже, значительно замедляет троичный оператор.
Когда я запускаю следующий код, вот мои результаты. Я думаю, что небольшое различие в миллисекундах вызвано тем, что компилятор оптимизирует max = max и удаляет его, но, вероятно, не выполняет эту оптимизацию для троичного оператора. Если бы кто-то мог проверить сборку и подтвердить это, это было бы здорово.
Код
источник
Если посмотреть на сгенерированный 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 (делая упреждающее чтение меньше)
источник
В следующем коде if / else кажется примерно в 1,4 раза быстрее, чем троичный оператор. Однако я обнаружил, что введение временной переменной уменьшает время выполнения троичного оператора примерно в 1,4 раза:
источник
Слишком много хороших ответов, но я нашел кое-что интересное, очень простые изменения дают о себе знать. После внесения изменений, приведенных ниже, выполнение if-else и троичного оператора займет одно и то же время.
вместо написания ниже строки
Я использовал это,
В одном из приведенных ниже ответов также отметим, что плохой способ написания троичного оператора.
Я надеюсь, что это поможет вам написать троичный оператор, а не думать о том, какой из них лучше.
Вложенный троичный оператор: я нашел вложенный троичный оператор и несколько, если блок else также будет выполняться одновременно.
источник