Является ли if (var == true) быстрее, чем if (var! = False)? - PullRequest
21 голосов
/ 29 сентября 2010

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

РЕДАКТИРОВАТЬ: Спасибо тем из вас, кто предоставил ответы.

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

И большое спасибо всем тем, кто высказал конструктивную критику.Я не знал о способности утверждать, если (вар) до сих пор.Я довольно уверен, что буду использовать его сейчас.;)

Ответы [ 10 ]

68 голосов
/ 29 сентября 2010

Прежде всего: единственный способ ответить на вопрос о производительности - измерить его . Попробуйте сами, и вы узнаете.

Что касается того, что делает компилятор: я напоминаю вам, что "если" - это просто условный переход. Когда у вас есть

if (x)
   Y();
else
   Z();
Q();

компилятор генерирует это как:

evaluate x
branch to LABEL1 if result was false
call Y
branch to LABEL2
LABEL1:
call Z
LABEL2:
call Q

или

evaluate !x
branch to LABEL1 if result was true

в зависимости от того, легче ли сгенерировать код для извлечения «нормального» или «инвертированного» результата для любого «x». Например, если у вас есть if (a<=b), может быть проще сгенерировать его как (if !(a>b)). Или наоборот; это зависит от деталей точного компилируемого кода.

Несмотря на это, я подозреваю, что у вас есть рыба побольше. Если вы заботитесь о производительности, используйте профилировщик и , найдите самую медленную вещь и затем исправьте это . Нет никакого смысла беспокоиться об оптимизации наносекунд, когда вы, вероятно, тратите целые миллисекунды где-то еще в вашей программе.

42 голосов
/ 29 сентября 2010

Знаете ли вы, что на процессорах x86 эффективнее делать x ^= x, где x - 32-разрядное целое число, чем x = 0?Это правда, и, конечно, имеет тот же результат.Следовательно, каждый раз, когда можно увидеть x = 0 в коде, можно заменить его на x ^= x и повысить эффективность.

Теперь, вы когда-нибудь видели x ^= x в большом количестве кода?

причина, по которой вы этого не сделали, заключается не только в том, что выигрыш в эффективности незначителен, но и в том, что это именно то изменение, которое будет делать компилятор (если компилируется в нативный код) или джиттер (если компилируется IL или подобное).Разберите некоторый код x86, и нередко можно увидеть ассемблерный эквивалент x ^= x, хотя код, скомпилированный для этого, почти наверняка имел x = 0 или, возможно, что-то намного более сложное, например x = 4 >> 6 или x = 32 - y, где анализкод показывает, что y всегда будет содержать 32 в этой точке и т. д.

По этой причине, хотя известно, что x ^= x более эффективен, подошва Эффект этого в подавляющем, подавляющем большинстве случаев будет делать код менее читабельным (единственное исключение будет в том случае, когда выполнение x ^= y влечет за собой использование алгоритма, и мы оказались в случае, когда xи y здесь были одинаковыми, в этом случае x ^= x сделает использование этого алгоритма более понятным, в то время как x = 0 скроет его).

В 99,999999% случаев применяется то же самоек вашему примеру.В остальных 0,000001% случаях это должно произойти, но есть разница в эффективности между некоторыми странными переопределениями операторов и , которые компилятор не может преобразовать один в другой.Действительно, 0,000001% преувеличивает случай и упоминается только потому, что я почти уверен, что если бы я попытался достаточно усердно, я мог бы написать что-то, где один был бы менее эффективным, чем другой.Обычно люди не очень стараются это сделать.

Если вы когда-нибудь посмотрите на свой собственный код в отражателе, вы, вероятно, найдете несколько случаев, когда он выглядит совсем не так, как написанный вами код.Причина этого заключается в том, что это обратный инжиниринг IL вашего кода, а не самого вашего кода, и действительно одна вещь, которую вы часто найдете, это такие вещи, как if(var == true) или if(var != false), превращающиеся в if(var) или даже вif(!var) с перевернутыми блоками if и else.

Посмотрите глубже, и вы увидите, что еще больше изменений сделано в том, что существует более одного способа убрать одного и того же кота.В частности, интересно посмотреть, как операторы switch преобразуются в IL;иногда он превращается в эквивалент множества if-else if операторов, а иногда он превращается в поиск в таблице переходов, которые можно было бы сделать, в зависимости от того, что казалось более эффективным в рассматриваемом случае.

Посмотрите еще глубже, и другие изменения будут внесены, когда он будет скомпилирован в собственный код.

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

18 голосов
/ 29 сентября 2010

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

private static void testVar(bool var)
{
    if (var == true)
    {
        Console.WriteLine("test");
    }

    if (var != false)
    {
        Console.WriteLine("test");
    }

    if (var)
    {
        Console.WriteLine("test");
    }
}

создает IL:

.method private hidebysig static void testVar(bool var) cil managed
{
  .maxstack 8
  L_0000: ldarg.0 
  L_0001: brfalse.s L_000d
  L_0003: ldstr "test"
  L_0008: call void [mscorlib]System.Console::WriteLine(string)
  L_000d: ldarg.0 
  L_000e: brfalse.s L_001a
  L_0010: ldstr "test"
  L_0015: call void [mscorlib]System.Console::WriteLine(string)
  L_001a: ldarg.0 
  L_001b: brfalse.s L_0027
  L_001d: ldstr "test"
  L_0022: call void [mscorlib]System.Console::WriteLine(string)
  L_0027: ret 
}

Таким образом, компилятор (в .Net 3.5) переводит их все в набор команд ldarg.0, brfalse.s.

11 голосов
/ 29 сентября 2010

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

(псевдо) сборка будет либо:

test reg1, reg2
br.true somewhere
; code for false case

somewhere:
; code for true case

или

test reg1, reg2
br.false somewhere
; code for true case

somewhere:
; code for false case

Какой из них выберет компилятор, не будет зависеть от того, пишете ли вы == true или != false. Скорее, это оптимизация, которую компилятор сделает на основе размера истинного и ложного кода случая и, возможно, некоторых других факторов.

Кроме того, код ядра Linux на самом деле пытается оптимизировать эти ветви, используя макросы LIKELY и UNLIKELY для своих условий if, так что я думаю, что это , возможно управлять им вручную .

11 голосов
/ 29 сентября 2010

Не вносит ощутимых различий, независимо от того, сколько итераций вы используете в своей программе.

(используйте вместо этого if (var); вам не нужен визуальный беспорядок сравнений.)

8 голосов
/ 29 сентября 2010

Практическое правило, которое обычно работает: «Если вы знаете, что они делают то же самое, то и компилятор знает».

Если компилятор знает, что две формы дают одинаковый результат, , тогда он выберет самую быструю .

Следовательно, предположите, что они одинаково быстры, пока ваш профилировщик не скажет вам иначе.

5 голосов
/ 29 сентября 2010

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

Более того, подобные вещи автоматически оптимизируются на любом приличном языке.

TL; доктор не беспокоить

4 голосов
/ 29 сентября 2010

Все остальные ответы хороши, я просто хотел добавить:

Это не значимый вопрос, , потому что он предполагает соотношение 1: 1 между нотацией и результирующим IL или собственным кодом.

Нет. И это верно даже в C ++ и даже в C. Вы должны пройти весь путь до нативного кода, чтобы такой вопрос имел смысл.

Отредактировано, чтобы добавить:

Разработчики первого компилятора Фортрана (около 1957 г.) однажды были удивлены, просматривая его результаты. Это испускал код, который не был явно правильным (хотя это было); по сути, он принимал решения по оптимизации, которые явно не были правильными (хотя они были).

Мораль этой истории: составители были умнее людей более 50 лет. Не пытайтесь перехитрить их, если вы не готовы проверить их результаты и / или не провести обширное тестирование производительности.

2 голосов
/ 29 сентября 2010

Это не имеет значения, и компилятор может свободно обмениваться ими по желанию.

Например, вы можете написать

if (!predicate)
    statement1;
else
    statement2;

, и компилятор может свободно генерировать код, эквивалентный

if (predicate)
    statement2;
else
    statement1;

или наоборот.

1 голос
/ 29 сентября 2010

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

Если вы программируете на ассемблере, скорее всего, вы знаете об этих двух случаях.лучше.Другие уже дали разбивку сборки в отношении операторов ветвления, поэтому я не буду дублировать эту часть ответа.Однако, по моему мнению, один элемент, который был пропущен, - это сравнение.

Возможно, что процессор может изменить флаги состояния после загрузки 'var'.Если это так, то, если 'var' равно 0, тогда нулевой флаг может быть установлен, когда переменная загружена в регистр.С таким процессором явного сравнения с FALSE не требуется.Эквивалентный псевдокод сборки будет ...

load 'var' into register
branch if zero or if not zero as appropriate

При использовании этого же процессора, если вы будете тестировать его на ИСТИНА, псевдокод сборки будет ...

load 'var' into register
compare that register to TRUE (a specific value)
branch if equal or if not equal as appropriate

На практике, ведут ли себя какие-либо процессоры так?Я не знаю - другие будут более осведомлены, чем я. Я знаю некоторых, которые не ведут себя таким образом, но я не знаю обо всех.

Предполагая, что некоторые процессоры ведут себя какв сценарии, описанном выше, что мы можем узнать?ЕСЛИ (и это большой ЕСЛИ) вы будете беспокоиться об этом, избегайте тестирования логических значений с явными значениями ...

if (var == TRUE)
if (var != FALSE)

и используйте одно из следующих для тестирования логических типов ...

if (var)
if (!var)
...