어느 것이 더 빠릅니까 : if (bool) 또는 if (int)?


94

어떤 값을 사용하는 것이 더 낫습니까? 부울 참 또는 정수 1?

위의 주제는 저와 몇 가지 실험을했다 boolintif조건. 그래서 호기심으로이 프로그램을 작성했습니다.

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
합리적인 최적화를 사용하여 비교하지 않는 한 불공정 한 테스트이기도합니다.
Daniel Pryden 2011-04-23

9
@Daniel : 둘 중 하나에 최적화 플래그를 사용하지 않습니다. 그러나 그것이 없어도 왜 더 많은 asm을 생성 하는가는 g(bool)합리적인 대답을 찾고있는 질문입니다.
Nawaz 2011

8
왜 asm을 읽는 데 어려움을 겪고 프로그램을 실행하고 결과를 타이밍하지 않습니까? 강사의 수는 실제로 성능에 대해 많이 말하지 않습니다. 명령어 길이뿐만 아니라 종속성 및 명령어 유형도 고려해야합니다 (일부는 더 느린 마이크로 코드 경로를 사용하여 디코딩됩니다. 실행 단위에 필요한 실행 단위, 명령어의 지연 시간 및 처리량은 무엇입니까?). 분기? 메모리 액세스?
jalf

2
@ 사용자 알 수 없음, @Malvolio : 분명히 그렇습니다. 프로덕션 코드를 위해이 모든 작업을 수행하지 않습니다. 내 게시물의 시작 부분에서 이미 언급했듯이 "호기심으로이 프로그램을 작성했습니다" . 네, 순전히 가설적인 것 입니다.
Nawaz 2011

3
합법적 인 질문입니다. 동등하거나 더 빠릅니다. ASM은 도움이되거나 큰 소리로 생각하기 위해 게시되었을 수 있으므로 질문을 피하고 "읽을 수있는 코드를 작성하십시오"라고 말하는 방법으로 사용하는 대신 질문에 답하거나 모르는 경우 STFU에 답하십시오. ;) 내 기여는 질문에 답할 수 있고 "그냥 읽을 수있는 코드를 작성하라"는 것은 질문을 피하는 것입니다.
Triynko 2011

답변:


99

나에게 의미가 있습니다. 컴파일러 bool는을 8 비트 값으로 정의하고 시스템 ABI에서는 호출 스택으로 푸시 할 때 작은 (<32 비트) 정수 인수를 32 비트로 "승격"하도록 요구합니다. 따라서 a를 비교하기 bool위해 컴파일러는 g가 수신하는 32 비트 인수의 최하위 바이트를 분리하는 코드를 생성하고이를 cmpb. 첫 번째 예에서 int인수는 스택에 푸시 된 전체 32 비트를 사용하므로 cmpl.


4
나는 동의한다. 이는 변수 유형을 선택할 때 잠재적으로 경쟁 할 수있는 두 가지 목적, 즉 저장 공간과 계산 성능을 위해 선택한다는 점을 밝히는 데 도움이됩니다.
Triynko 2011

3
이 또한, 64 비트 프로세스에 적용됩니까 __int64보다 빠르다 int? 아니면 CPU가 32 비트 정수와 32 비트 명령어 세트를 별도로 처리합니까?
Crend King 2011-04-27

1
@CrendKing 아마도 다른 질문을 할 가치가 있습니까?
표시 이름

81

로 컴파일 -03하면 다음 이 제공됩니다.

에프:

    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

.. 그래서를 제외하고 기본적으로 동일한 코드로 컴파일 cmplcmpb. 이것은 차이가있는 경우 중요하지 않음을 의미합니다. 최적화되지 않은 코드로 판단하는 것은 공정하지 않습니다.


내 요점을 명확히하기 위해 편집하십시오 . 최적화되지 않은 코드는 속도가 아닌 간단한 디버깅을위한 것입니다. 최적화되지 않은 코드의 속도를 비교하는 것은 무의미합니다.


8
귀하의 결론에 동의하는만큼 흥미로운 부분을 건너 뛰고있는 것 같습니다. cmpl 하나와 cmpb다른 하나를 사용 합니까?
jalf 2011

22
@jalf : a bool는 단일 바이트이고 an int은 4 이기 때문 입니다. 그보다 더 특별한 것은 없다고 생각합니다.
CB Bailey

7
다른 응답자들은 그 이유에 대해 더 많은 관심을 기울 였다고 생각합니다. 문제의 플랫폼 bool이 8 비트 유형으로 취급되기 때문 입니다.
Alexander Gessler 2011

9
@Nathan : 아니요. C ++에는 비트 데이터 유형이 없습니다. 가장 작은 유형은 char정의에 따라 바이트이며 주소 지정이 가능한 가장 작은 단위입니다. bool의 크기는 구현에 따라 정의되며 1, 4 또는 8 등이 될 수 있습니다. 하지만 컴파일러는 그것을 하나로 만드는 경향이 있습니다.
GManNickG 2011

6
@Nathan : 자바에서도 그게 까다 롭습니다. Java는 부울이 나타내는 데이터가 1 ​​비트의 값이라고 말하지만 해당 비트가 저장되는 방법은 여전히 ​​구현 정의되어 있습니다. 실용적인 컴퓨터는 단순히 비트를 처리하지 않습니다.
GManNickG 2011

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부울과 cmplint의 경우) 그렇지 않으면 본문이 동일합니다. 인텔 매뉴얼을 훑어 보면 알 수 있습니다. ...별로 중요하지 않습니다. 같은 것은 없습니다 cmpb또는 cmpl인텔 매뉴얼에가. 모두 cmp이고 지금은 타이밍 테이블을 찾을 수 없습니다. 그러나 나는 즉시 바이트를 비교하는 것과 긴 즉시를 비교하는 것 사이에 클럭 차이가 없다고 추측하고 있으므로 모든 실제 목적에서 코드는 동일합니다.


귀하의 추가 사항에 따라 다음을 추가하도록 편집되었습니다.

최적화되지 않은 경우 코드가 다른 이유는 최적화되지 않았기 때문입니다. (예, 원형입니다. 알고 있습니다.) 컴파일러가 AST를 직접 탐색하고 코드를 생성 할 때 AST가 들어있는 즉각적인 지점에있는 것을 제외하고는 아무것도 "알지"못합니다. 그 지점에서 필요한 모든 컨텍스트 정보가 부족합니다. 이 특정 지점에서 선언 된 유형 boolint. 부울은 기본적으로 바이트로 취급되며 Intel 세계에서 바이트를 조작 할 때 특정 너비로 ​​가져 와서 스택에 넣는 등의 작업을 수행해야합니다. (바이트를 푸시 할 수 없습니다. .)

그러나 옵티마이 저가 AST를보고 마법을 수행 할 때 주변 컨텍스트를보고 의미를 변경하지 않고 코드를 더 효율적인 것으로 대체 할 수있는시기를 "알고"있습니다. 따라서 매개 변수에 정수를 사용할 수 있으므로 불필요한 변환 및 확장을 잃을 수 있다는 것을 "알고"있습니다.


1
하하, 컴파일러가 단순히 99 또는 99 + 58 = 157 = -99 (서명 된 8 비트 오버플로)를 반환하는 방식이 마음에 듭니다.
deceleratedcaviar

@ 다니엘 : 나도 좋아 했어요. 처음에는 "-99는 어디에 있습니까?"라고 말했고 즉시 나는 그것이 매우 변태적이라는 것을 깨달았습니다.
Nawaz 2011

7
l그리고 bAT & T에서 사용 접미사 전용 구문된다. 이들은 각각 cmp4 바이트 (long) 및 1 바이트 (바이트) 피연산자 를 사용 하는 버전을 나타냅니다. 인텔 구문 모호성이있는 경우, 통상적으로 메모리 피연산자와 태그가 BYTE PTR, WORD PTRDWORD PTR대신 연산 코드에 접미사를 넣는.
CB Bailey

타이밍 테이블 : agner.org/optimize 두 피연산자 크기 모두 cmp동일한 성능을 가지며 읽기에 대한 부분 레지스터 페널티가 없습니다 %dil. (그러나 그것은 clang and이 99와 -99 사이의 분기없는 대소 문자 넘김의 일환으로 AL에서 바이트 크기 를 사용하여 부분 등록 중단을 만드는 것을 막지는 못합니다 .)
Peter Cordes

13

Linux 및 Windows에서 GCC 4.5 이상을 사용하면 sizeof(bool) == 1. x86 및 x86_64에서는 범용 레지스터보다 적은 값을 함수에 전달할 수 없습니다 (호출 규칙에 따라 스택 또는 레지스터 등을 통해).

따라서 bool에 대한 코드는 최적화되지 않은 경우 실제로 인수 스택에서 해당 bool 값을 추출하기 위해 일정 길이로 이동합니다 (다른 스택 슬롯을 사용하여 해당 바이트를 저장). 네이티브 레지스터 크기의 변수를 가져 오는 것보다 더 복잡합니다.


C ++ 03 표준에서 §5.3.3 / 1 : " sizeof(bool)sizeof(wchar_t)구현 정의 됨 "따라서 sizeof(bool) == 1특정 컴파일러의 특정 버전에 대해 이야기하지 않는 한이 말 은 엄격하게 정확하지 않습니다.
ildjarn 2011

9

기계 수준에서는 bool과 같은 것이 없습니다.

거의 모든 종류의 부울 피연산자 유형을 정의하는 명령어 세트 아키텍처는 거의 없지만 0이 아닌 값에 대해 작업을 트리거하는 명령어가있는 경우가 많습니다. CPU에게 일반적으로 모든 것은 스칼라 유형 중 하나 또는 문자열입니다.

주어진 컴파일러와 주어진 ABI가 특정 크기를 선택해야합니다 intbool귀하의 경우처럼,이들은 약간 다른 코드를 생성 할 수 있으며, 최적화 하나의 몇 가지 수준에서 약간 빠를 수 있습니다 다른 크기는 때.

많은 시스템에서 bool이 1 바이트 인 이유는 무엇입니까?

char누군가가 정말 많은 배열을 만들 수 있기 때문에 bool 유형 을 선택하는 것이 더 안전 합니다.

업데이트 : 에 의해 "안전이" 내 말은 : 컴파일러와 라이브러리를 구현합니다. 사람들이 시스템 유형을 다시 구현해야한다고 말하는 것이 아닙니다.


2
+1 bool비트로 표현 된 경우 x86의 오버 헤드를 상상해보십시오 . 따라서 바이트는 많은 구현에서 속도 / 데이터 압축에 대한 좋은 절충안이 될 것입니다.
hardmath 2011

1
@Billy : 나는 그가 " char대신 사용"을 말하는 bool것이 아니라 char컴파일러가 bool개체에 대해 선택한 크기를 참조 할 때 "1 바이트"를 의미 하는 " 유형"을 사용 했다고 생각 합니다.
Dennis Zickefoose 2011

오, 물론, 각 프로그램이 선택해야한다는 의미는 아니 었습니다. 시스템 bool 유형이 1 바이트 인 이유에 대한 근거를 제시하는 것뿐입니다.
DigitalRoss 2011

@Dennis : 아, 말이 되네요.
Billy ONeal 2011

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

3 천만 개의 루프에 걸쳐 각 루프마다 1 개의 할당과 2 개의 비교를 사용하는 경우 동일합니다. 최적화 할 다른 것을 찾으십시오. 예를 들어 strcmp를 불필요하게 사용하지 마십시오. ;)



0

두 가지 방법으로 질문에 접근합니다.

C ++ 또는 해당 문제에 대한 어셈블리 코드를 생성하는 프로그래밍 언어에 대해 구체적으로 이야기하는 경우 컴파일러가 ASM에서 생성 할 코드에 묶여 있습니다. 우리는 또한 C ++에서 true와 false의 표현에 묶여 있습니다. 정수는 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플래그 설정을위한 빼기를 의미 하므로 bool은 약간 빠릅니다 . 또한 컴파일러가 생성 한 것과 모순됩니다.

훨씬 더 간단한 또 ​​다른 접근 방식은 표현식의 논리를 자체적으로 살펴보고 컴파일러가 코드를 어떻게 번역할지 걱정하지 않는 것입니다. 이것이 훨씬 더 건전한 사고 방식이라고 생각합니다. 나는 궁극적으로 컴파일러에 의해 생성되는 코드가 실제로 진실한 해결책을 제공하려고 노력하고 있다고 믿습니다. 내 말은 if 문에서 테스트 케이스를 늘리고 한쪽에는 부울을 다른쪽에는 정수를 고수하면 컴파일러가 생성 된 코드가 기계 수준의 부울 식으로 더 빠르게 실행되도록 만들 것입니다.

나는 이것이 개념적 질문이라고 생각하고 있으므로 개념적 대답을 할 것입니다. 이 토론은 코드 효율성이 어셈블리의 코드 줄을 줄이는 지 여부에 대해 일반적으로 논의하는 내용을 상기시킵니다. 이 개념은 일반적으로 사실로 받아 들여지는 것 같습니다. ALU가 각 명령문을 처리하는 속도를 추적하는 것이 실행 가능하지 않다는 점을 고려할 때 두 번째 옵션은 어셈블리에서 점프 및 비교에 초점을 맞추는 것입니다. 이 경우 제시 한 코드에서 부울 문 또는 정수 간의 구별이 다소 대표적입니다. C ++에서 표현식의 결과는 값을 반환 한 다음 표현이 제공됩니다. 반면에 조립에서는 점프 및 비교는 C ++ if 문에서 다시 평가되는 표현식 유형에 관계없이 숫자 값을 기반으로합니다. 이러한 질문에 대해 이와 같은 순전히 논리적 인 명령문은 결국 엄청난 계산 오버 헤드가 발생한다는 것을 기억하는 것이 중요합니다. 비록 단일 비트가 같은 일을 할 수 있더라도 말입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.