Что быстрее: if (bool) или if (int)?

94

Какое значение лучше использовать? Логическое истина или целое число 1?

Выше тема заставила меня сделать некоторые эксперименты с boolи intв ifсостоянии. Я просто из любопытства написал эту программу:

int f(int i) 
{
    if ( i ) return 99;   //if(int)
    else  return -99;
}
int g(bool b)
{
    if ( b ) return 99;   //if(bool)
    else  return -99;
}
int main(){}

g++ intbool.cpp -S генерирует asm-код для каждой функции следующим образом:

  • asm-код для f(int)

    __Z1fi:
       LFB0:
             pushl  %ebp
       LCFI0:
              movl  %esp, %ebp
       LCFI1:
              cmpl  $0, 8(%ebp)
              je    L2
              movl  $99, %eax
              jmp   L3
       L2:
              movl  $-99, %eax
       L3:
              leave
       LCFI2:
              ret
    
  • asm-код для g(bool)

    __Z1gb:
       LFB1:
              pushl %ebp
       LCFI3:
              movl  %esp, %ebp
       LCFI4:
              subl  $4, %esp
       LCFI5:
              movl  8(%ebp), %eax
              movb  %al, -4(%ebp)
              cmpb  $0, -4(%ebp)
              je    L5
              movl  $99, %eax
              jmp   L6
       L5:
              movl  $-99, %eax
       L6:
              leave
       LCFI6:
              ret
    

На удивление g(bool)генерирует больше asmинструкций! Значит ли это, что if(bool)немного медленнее, чем if(int)? Раньше я думал, что boolон специально разработан для использования в условных операторах, таких как if, поэтому я ожидал, что g(bool)будет генерировать меньше инструкций asm, что сделает его g(bool)более эффективным и быстрым.

РЕДАКТИРОВАТЬ:

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


Наваз
источник
32
Это также несправедливый тест, если вы не сравните их с включенной разумной оптимизацией.
Дэниел Прайден
9
@ Дэниел: Я не использую никаких флагов оптимизации ни с одним из них. Но даже при его отсутствии, почему он генерирует больше asm, g(bool)- это вопрос, на который я ищу разумный ответ.
Nawaz
8
Зачем вам нужно читать asm, а не просто запускать программу и рассчитывать результат ? Количество инструкций мало что говорит о производительности. Вам необходимо учитывать не только длину инструкций, но также зависимости и типы инструкций (некоторые из них декодируются с использованием более медленного пути микрокода, какие исполнительные единицы им требуются, какова задержка и пропускная способность инструкции, является ли это ветка? Доступ к памяти?
jalf
2
@user unknown и @Malvolio: Это очевидно; Я делаю все это не для производственного кода. Как я уже упоминал в начале своего поста, «Я просто из любопытства написал эту программу» . Так что да, это чисто гипотетический .
Nawaz
3
Законный вопрос. Они либо эквивалентны, либо один быстрее. ASM, вероятно, был размещен в попытке помочь или подумать вслух, поэтому вместо того, чтобы использовать его как способ уклониться от вопроса и сказать «просто напишите читаемый код», просто ответьте на вопрос или STFU, если вы не знаете или не имею ничего полезного сказать;) Мой вклад в том, что на вопрос можно ответить, а «просто напишите читаемый код» - не что иное, как уклонение от вопроса.
Трийнко

Ответы:

99

Для меня это имеет смысл. Ваш компилятор, по-видимому, определяет a boolкак 8-битное значение, а ABI вашей системы требует, чтобы он «продвигал» маленькие (<32-битные) целочисленные аргументы до 32-битных при их добавлении в стек вызовов. Итак, чтобы сравнить a bool, компилятор генерирует код для выделения младшего байта 32-битного аргумента, который получает g, и сравнивает его с cmpb. В первом примере intаргумент использует полные 32 бита, которые были помещены в стек, поэтому он просто сравнивается со всем этим с cmpl.

Шерм Пендли
источник
4
Согласен. Это помогает понять, что, выбирая тип переменной, вы выбираете ее для двух потенциально конкурирующих целей: пространство для хранения и производительность вычислений.
Трийнко
3
Это также относится к 64-битным процессам, которые __int64быстрее, чем int? Или процессор обрабатывает 32-битные целые числа с 32-битными наборами инструкций отдельно?
Crend King
1
@CrendKing может стоит накатить еще вопрос?
Отображаемое имя
81

Компиляция с помощью -03дает мне следующее:

f:

    pushl   %ebp
    movl    %esp, %ebp
    cmpl    $1, 8(%ebp)
    popl    %ebp
    sbbl    %eax, %eax
    andb    $58, %al
    addl    $99, %eax
    ret

грамм:

    pushl   %ebp
    movl    %esp, %ebp
    cmpb    $1, 8(%ebp)
    popl    %ebp
    sbbl    %eax, %eax
    andb    $58, %al
    addl    $99, %eax
    ret

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


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

Александр Гесслер
источник
8
Насколько я согласен с вашим выводом, я думаю, что вы упускаете интересную часть. Почему он используется cmplдля одного, а cmpbдля другого?
jalf
22
@jalf: Потому что a bool- это один байт, а an int- четыре. Я не думаю, что есть что-то более особенное, чем это.
CB Bailey
7
Я думаю, что в других ответах больше внимания уделялось причинам: это потому, что рассматриваемая платформа рассматривается boolкак 8-битный тип.
Александр Гесслер
9
@Nathan: Нет. В C ++ нет битовых типов данных. Наименьший тип - charэто байт по определению и наименьшая адресуемая единица. boolразмер определяется реализацией и может быть 1, 4, 8 или что угодно. Однако компиляторы, как правило, делают это одним.
GManNickG
6
@Nathan: Ну, с Java тоже сложно. В Java говорится, что данные, представляемые логическим значением, являются значением одного бита, но то, как этот бит сохраняется, все еще определяется реализацией. Прагматичные компьютеры просто не адресуют биты.
GManNickG
26

Когда я компилирую это с разумным набором параметров (в частности, -O3), вот что я получаю:

Для f():

        .type   _Z1fi, @function
_Z1fi:
.LFB0:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        cmpl    $1, %edi
        sbbl    %eax, %eax
        andb    $58, %al
        addl    $99, %eax
        ret
        .cfi_endproc

Для g():

        .type   _Z1gb, @function
_Z1gb:
.LFB1:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        cmpb    $1, %dil
        sbbl    %eax, %eax
        andb    $58, %al
        addl    $99, %eax
        ret
        .cfi_endproc

Они по-прежнему используют разные инструкции для сравнения ( cmpbдля логических и cmplдля int), но в остальном тела идентичны. Беглый взгляд на руководства Intel говорит мне: ... ничего особенного. В руководствах Intel нет такого понятия, как cmpbили cmpl. Они все, cmpи я пока не могу найти таблицы времени. Однако я предполагаю, что нет разницы в часах между сравнением непосредственного байта и сравнением длинного немедленного, поэтому для всех практических целей код идентичен.


отредактировал, чтобы добавить следующее на основе вашего добавления

Причина, по которой код отличается в неоптимизированном случае, заключается в том, что он не оптимизирован. (Да, это циклично, я знаю.) Когда компилятор обходит AST и напрямую генерирует код, он ничего не «знает», кроме того, что находится в непосредственной точке AST, в котором он находится. В этот момент ему не хватает всей необходимой контекстной информации. чтобы знать, что в этот конкретный момент он может рассматривать объявленный тип boolкак int. Очевидно, что логическое значение по умолчанию обрабатывается как байт, и при манипулировании байтами в мире Intel вы должны делать такие вещи, как расширение знака, чтобы довести его до определенной ширины, чтобы поместить его в стек и т. Д. (Вы не можете протолкнуть байт .)

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

ТОЛЬКО МОЕ ПРАВИЛЬНОЕ МНЕНИЕ
источник
1
Ха-ха, мне нравится, как компилятор просто вернул 99, или 99 + 58 = 157 = -99 (переполнение подписанных 8 бит) ... интересно.
deceleratedcaviar
@ Даниэль: Даже мне это понравилось. Сначала я сказал «где -99» и сразу понял, что это что-то очень странное.
Nawaz
7
lи bсуффиксы используются только в синтаксисе AT&T. Они просто относятся к версиям cmpиспользования 4-байтовых (длинных) и 1-байтовых (байтовых) операндов соответственно. Если есть какая - либо двусмысленность в синтаксисе Intel, обычно операнд памяти с тегами BYTE PTR, WORD PTRили DWORD PTRвместо того , чтобы положить суффикс опкода.
CB Bailey,
Таблицы синхронизации: agner.org/optimize Оба размера операнда cmpимеют одинаковую производительность, и нет никаких штрафов за чтение частичного регистра %dil. (Но это не останавливает лязг от забавного создания лабиринта с частичным регистром, используя размер байта andна AL как часть безотходного переключения регистра между 99 и -99.)
Питер Кордес,
13

По крайней мере, с GCC 4.5 в Linux и Windows, sizeof(bool) == 1 . На x86 и x86_64 вы не можете передать функции меньше, чем значение регистра общего назначения (будь то через стек или регистр, в зависимости от соглашения о вызовах и т. Д.).

Таким образом, код для bool, когда он не оптимизирован, фактически достигает некоторой длины, чтобы извлечь это значение bool из стека аргументов (используя другой слот стека для сохранения этого байта). Это сложнее, чем просто получить переменную размера собственного регистра.

Мат
источник
С ++ 03 стандарта C, §5.3.3 / 1: « sizeof(bool)и sizeof(wchar_t)в реализации. » Так говоря , sizeof(bool) == 1не совсем корректно , если вы говорите о конкретной версии конкретного компилятора.
ildjarn
9

На машинном уровне такого понятия, как bool, не существует.

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

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

Почему во многих системах bool имеет один байт?

Безопаснее выбирать charтип для bool, потому что кто-то может создать действительно большой массив.

Обновление: под «безопаснее» я имею в виду: для разработчиков компилятора и библиотеки. Я не говорю, что людям нужно заново реализовать тип системы.

DigitalRoss
источник
2
+1 Представьте себе накладные расходы на x86, если бы они boolбыли представлены битами; так что байт будет хорошим компромиссом для скорости / компактности данных во многих реализациях.
hardmath
1
@Billy: Я думаю, он не говорил «использовать charвместо bool», а просто использовал « charтип» для обозначения «1 байт», когда имел в виду размер, который компилятор выбирает для boolобъектов.
Деннис Зикефуз
О, конечно, я не имел в виду, что каждая программа должна выбирать, я просто приводил обоснование того, почему системный тип bool составляет 1 байт.
DigitalRoss
@ Деннис: Ах, в этом есть смысл.
Билли Онил
7

Да, обсуждение весело. Но просто проверьте это:

Код теста:

#include <stdio.h>
#include <string.h>

int testi(int);
int testb(bool);
int main (int argc, char* argv[]){
  bool valb;
  int  vali;
  int loops;
  if( argc < 2 ){
    return 2;
  }
  valb = (0 != (strcmp(argv[1], "0")));
  vali = strcmp(argv[1], "0");
  printf("Arg1: %s\n", argv[1]);
  printf("BArg1: %i\n", valb ? 1 : 0);
  printf("IArg1: %i\n", vali);
  for(loops=30000000; loops>0; loops--){
    //printf("%i: %i\n", loops, testb(valb=!valb));
    printf("%i: %i\n", loops, testi(vali=!vali));
  }
  return valb;
}

int testi(int val){
  if( val ){
    return 1;
  }
  return 0;
}
int testb(bool val){
  if( val ){
    return 1;
  }
  return 0;
}

Скомпилировано на 64-битном ноутбуке с Ubuntu 10.10 с помощью: g ++ -O3 -o / tmp / test_i /tmp/test_i.cpp

Целочисленное сравнение:

sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.203s
user    0m8.170s
sys 0m0.010s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.056s
user    0m8.020s
sys 0m0.000s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.116s
user    0m8.100s
sys 0m0.000s

Логический тест / печать без комментариев (и с целыми комментариями):

sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.254s
user    0m8.240s
sys 0m0.000s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m8.028s
user    0m8.000s
sys 0m0.010s
sauer@trogdor:/tmp$ time /tmp/test_i 1 > /dev/null

real    0m7.981s
user    0m7.900s
sys 0m0.050s

Они одинаковы с 1 присваиванием и 2 сравнениями в каждом цикле более 30 миллионов циклов. Найдите что-нибудь еще для оптимизации. Например, не используйте strcmp без надобности. ;)

Dannysauer
источник
2

В основном это будет зависеть от компилятора и оптимизации. Здесь есть интересное обсуждение (без языковой привязки):

Требует ли «if ([bool] == true)» на один шаг больше, чем «if ([bool])»?

Также взгляните на этот пост: http://www.linuxquestions.org/questions/programming-9/c-compiler-handling-of-boolean-variables-290996/

Алеадам
источник
0

Подходя к вашему вопросу двумя разными способами:

Если вы конкретно говорите о C ++ или любом языке программирования, который будет производить ассемблерный код в этом отношении, мы привязаны к тому, какой код компилятор будет генерировать в ASM. Мы также связаны с представлением истинного и ложного в C ++. Целое число должно быть 32-битным, и я мог бы просто использовать байт для хранения логического выражения. Фрагменты asm для условных операторов:

Для целого числа:

  mov eax,dword ptr[esp]    ;Store integer
  cmp eax,0                 ;Compare to 0
  je  false                 ;If int is 0, its false
  ;Do what has to be done when true
false:
  ;Do what has to be done when false

Для bool:

  mov  al,1     ;Anything that is not 0 is true
  test al,1     ;See if first bit is fliped
  jz   false    ;Not fliped, so it's false
  ;Do what has to be done when true
false:
  ;Do what has to be done when false

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

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

Я считаю, что это концептуальный вопрос, поэтому дам концептуальный ответ. Это обсуждение напоминает мне о дискуссиях, которые я обычно веду о том, приводит ли эффективность кода к меньшему количеству строк кода в сборке. Кажется, что эта концепция общепринята как истинная. Учитывая, что отслеживать, насколько быстро ALU будет обрабатывать каждый оператор, нецелесообразно, второй вариант - сосредоточиться на переходах и сравнении в сборке. В этом случае различие между логическими операторами и целыми числами в представленном вами коде становится довольно характерным. Результат выражения в C ++ вернет значение, которому затем будет дано представление. С другой стороны, в сборке переходы и сравнения будут основаны на числовых значениях независимо от того, какой тип выражения оценивался в вашем C ++ операторе if. При ответе на эти вопросы важно помнить, что чисто логические утверждения, подобные этим, приводят к огромным вычислительным затратам, даже если один бит способен на то же самое.

Арти
источник