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




какое значение лучше использовать? Логическое значение true или целое число 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 код для f(int)



    __Z1fi:
    LFB0:
    pushl %ebp
    LCFI0:
    movl %esp, %ebp
    LCFI1:
    cmpl , 8(%ebp)
    je L2
    movl , %eax
    jmp L3
    L2:
    movl $-99, %eax
    L3:
    leave
    LCFI2:
    ret



  • ASM код для g(bool)



    __Z1gb:
    LFB1:
    pushl %ebp
    LCFI3:
    movl %esp, %ebp
    LCFI4:
    subl , %esp
    LCFI5:
    movl 8(%ebp), %eax
    movb %al, -4(%ebp)
    cmpb , -4(%ebp)
    je L5
    movl , %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) более эффективный и быстрый.



EDIT:



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




912   8  

8 ответов:

имеет смысл для меня. Ваш компилятор, по-видимому, определяет bool Как 8-битное значение, и ваша система ABI требует, чтобы она" продвигала " небольшие (bool, компилятор генерирует код для выделения наименее значимого байта 32-разрядного аргумента, который получает g, и сравнивает его с cmpb. В первом примере int аргумент использует полные 32 бита, которые были помещены в стек, поэтому он просто сравнивает против всего этого с cmpl.

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

f:

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

g:

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

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


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

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

на f():

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

на g():

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

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


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

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

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

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

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

на машинном уровне нет такой вещи, как bool

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

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

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

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

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

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

тестовый код:

#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 без необходимости. ;)

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

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

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

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

Если вы конкретно говорите о 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

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

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

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

Comments

    Ничего не найдено.