Что быстрее: if (bool) или if (int)? - PullRequest
88 голосов
/ 23 апреля 2011

Какое значение лучше использовать?Boolean true или Integer 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.Но это не вопрос.Вопрос в том, что я спросил.


Ответы [ 8 ]

97 голосов
/ 23 апреля 2011

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

77 голосов
/ 23 апреля 2011

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

f:

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

g:

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

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


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

26 голосов
/ 23 апреля 2011

Когда я компилирую это с помощью нормального набора параметров (в частности, -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 для целого), но в остальном тела идентичны. Беглый взгляд на руководства Intel говорит мне: ... ничего особенного. В руководствах Intel нет такой вещи, как cmpb или cmpl. Они все cmp, и я не могу найти таблицы времени на данный момент. Я предполагаю, однако, что нет никакой разницы в тактах между сравнением байтового немедленного и сравнением длинного немедленного, поэтому для всех практических целей код идентичен.


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

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

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

13 голосов
/ 23 апреля 2011

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

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

9 голосов
/ 24 апреля 2011

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

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

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

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

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

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

7 голосов
/ 24 апреля 2011

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

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

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

2 голосов
/ 23 апреля 2011

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

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

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

0 голосов
/ 12 декабря 2011

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

Если вы конкретно говорите о 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 и добавите логическое значение с одной стороны и целое число с другой, компилятор сделает это так, что сгенерированный код будет выполняться быстрее с логическими выражениями на машинном уровне.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...