source

컴파일러에서 8비트로 표시되는 부울 값.운영이 비효율적인가요?

goodcode 2022. 7. 23. 14:13
반응형

컴파일러에서 8비트로 표시되는 부울 값.운영이 비효율적인가요?

Agner Fog의 "Optimizing software in C++"(인텔, AMD 및 VIA용 x86 프로세서 전용)를 읽고 있으며 34페이지에 기재되어 있습니다.

부울 변수는 false 값 0, true 값 1의 8비트 정수로 저장됩니다.부울 변수는 입력으로 부울 변수를 가진 모든 연산자가 입력이 0 또는 1이 아닌 다른 값을 가지는지 확인하지만 출력으로 부울 변수를 가진 연산자는 0 또는 1 이외의 값을 생성할 수 없다는 점에서 지나치게 결정됩니다.이로 인해 부울 변수를 입력으로 사용하는 작업이 필요 이상으로 효율적이지 않습니다.

어떤 컴파일러에서 이것이 오늘날에도 적용됩니까?예를 들어주실 수 있나요?저자는 말한다.

오퍼랜드에 0과 1 이외의 값이 없음을 확실히 알고 있으면 부울 연산을 훨씬 효율적으로 할 수 있습니다.컴파일러가 이러한 가정을 하지 않는 이유는 변수가 초기화되지 않았거나 알 수 없는 소스에서 나온 경우 다른 값을 가질 수 있기 때문입니다.

, 함수 포인터 「」를 하면, 「」가 되는 입니까.bool(*)()예를 들어, 그 운영으로 인해 비효율적인 코드가 생성되는 경우가 있습니다.또는 포인터를 참조 해제하거나 참조에서 읽음으로써 부울에 액세스한 후 조작하는 경우입니까?

TL: DR: 현재 컴파일러에는 아직bool를 놓침
(a&&b) ? x : y하지만 그 이유는 0/1을 상정하지 않기 때문이 아니라 단지 이 일에 서툴러서이다.

bool 또는 은 '로컬' 또는 ' 함수용입니다.01는, 원래의 조건으로 최적화를 실시해 분기(또는 cmov 등)할 수 있습니다.에만 을 쓴다.bool입력/출력은 인라인 상태가 아니거나 메모리에 실제로 저장되어 있지 않은 것을 통과/전환해야 할 때 발생합니다.

가능한 최적화 가이드라인: combineboolmemory)에서 비트 args/memory)를 사용하여 s를 지정합니다.a&bMSVC의 ICC입니다.가 더 bool s. 하라.a&b와 동등하다.a&&b★★★★★★에bool, , , , , , , , , , , , , , , , , , , , , , , 2 && 1 '''는)2 & 10이 틀리다or or or or 이 에 없 다 다 없 없 。

이 가이드라인이 함수 내(또는 삽입된 내용)의 비교에서 설정된 현지인에게 피해를 줄 수 있는지 확인합니다.예를 들어 컴파일러는 가능한 한 직접 비교 결과를 사용하는 대신 실제로 정수 부란을 만들 수 있습니다.또한 현재 gcc와 clang에는 도움이 되지 않는 것 같습니다.


스토어에서의 C, x86 스토어에서의 C++ 실장bool(적어도 컴파일러가 이를 필요로 하는 ABI/호출 규칙을 준수해야 하는 함수 호출 경계를 넘나들며) 항상 0 또는 1인 바이트 단위입니다.

가 이를 이용하는 를 들어, 컴파일러는 이을 이용합니다.bool->intconversion even 4.gcc 4.4비트 32비트)로 간단하게 합니다.movzx eax, dil). Clang과 MSVC도 이 작업을 수행합니다.C 및 C++ 규칙에서는 이 변환이 0 또는1을 생성하기 위해 필요합니다.따라서 이 동작은 항상 안전하다고 가정할 경우에만 안전합니다.boolfunction arg 글로벌 변수 "0" "1"

으로는 이 하고 .bool->int지른따라서 애그너가 말한 이유는 틀렸습니다.

컴파일러가 이러한 가정을 하지 않는 이유는 변수가 초기화되지 않았거나 알 수 없는 소스에서 나온 경우 다른 값을 가질 수 있기 때문입니다.


는 MSVC CL19를 가정한 .boolarg 0 abi 1 、 Windows x86 - 64 ABI の 는이 。

x86-64 System V ABI(Windows 이외의 모든 것에 의해 사용됨)에서는 리비전 0.98의 changelog에 다음과 같이 기술되어 있습니다._Bool 명kaka (bool되어 있습니다.)」, 「부울화되어 있습니다.」, 「부울화되어 있습니다라고 하는 변경 상정하고 만, 가 이미 것을 에 지나지 않습니다.그 변경 전부터 컴파일러가 그것을 상정하고 있었다고 생각합니다만, 이것은 컴파일러가 이미 이용하고 있던 것을 나타내고 있을 뿐입니다.는 다음과 x86-64 SysV ABI는 다음과 같습니다.

3.1.2 데이터의 표현

부울란은 메모리 개체에 저장될 때 값이 항상 0(false) 또는 1(true)인 단일 바이트 개체로 저장됩니다.정수 레지스터에 저장되는 경우(인수로 전달되는 경우를 제외하고), 레지스터의 8바이트는 모두 중요합니다.제로가 아닌 값은 true로 간주됩니다.

두 번째 문장은 난센스입니다.ABI는 컴파일러에게 다른 컴파일 유닛(메모리/함수 arg 및 반환값) 사이의 경계에서만 함수 내부의 레지스터에 저장하는 방법을 지시할 수 없습니다.저는 이 ABI 결함을 얼마 전에 Github 페이지에 보고했습니다.

3.2.3 매개 변수 전달:

type 값인 경우_Bool레지스터 또는 스택에서 반환 또는 전달되며 비트 0에는 진실 값이 포함되며 비트 1~7은 0이어야16 합니다.

(제16장):다른 비트는 지정되지 않은 상태로 유지되므로 이들 값의 소비자 측은 8비트로 잘라낸 경우 0 또는 1이 될 수 있습니다.

i386 System V ABI의 언어는 IIRC와 동일합니다.


0/1을 전제로 하는 컴파일러(예: 변환)int)는 최적화를 놓치는 경우가 있을 경우 이점을 활용하지 못하는 경우도 있습니다.안타깝게도 이러한 최적화 누락은 여전히 존재하지만, Agner가 컴파일러에 대해 썼을 때보다 더 드문 경우입니다.

(gcc4.6/4.7 및 clang/MSVC용 Godbolt 컴파일러 탐색기의 소스 + asm).Matt Godbolt의 CppCon2017 토크 "내 컴파일러가 최근에 내게 한 일"을 참조하십시오. 컴파일러의 뚜껑을 열다)

bool logical_or(bool a, bool b) { return a||b; }

 # gcc4.6.4 -O3 for the x86-64 System V ABI
    test    dil, dil            # test a against itself (for non-zero)
    mov     eax, 1
    cmove   eax, esi            # return   a ? 1 : b;
    ret

gcc4.6도 재냉동되지 않았습니다.b단, gcc4.7이 제공하는 최적화는 놓쳤습니다. (다른 답변과 같이 clang 및 그 이후의 컴파일러도 마찬가지입니다.)

    # gcc4.7 -O3 to present: looks ideal to me.
    mov     eax, esi
    or      eax, edi
    ret

(딸랑딸랑 소리)or dil, sil/mov eax, edi바보같다: Nehalem 또는 이전 인텔에서 읽기 시 부분 등록이 정지되는 것이 보증된다.edi글을 쓴 후에diledi의 로우8 부분을 사용하기 위해 REX 프리픽스가 필요하기 때문에 코드 사이즈가 나빠집니다.더 나은 선택은or dil,sil/movzx eax, dil발신자가 일부 arg-bit 레지스터를 "partial" 레지스터가 있는 경우 32비트 레지스터를 읽지 않도록 합니다.)

MSVC는 이 코드를 발행하여 개별적으로 체크하고 아무것도 이용하지 않으며xor al,al대신xor eax,eax즉, 이것은 오래된 가치의 잘못된 의존성을 가지고 있습니다.eax대부분의 CPU(하스웰/스카이레이크 포함), 전체 레지스터와 별도로 low-8 부분 레지스트의 이름을 바꾸지 않고 AH/BH/...만 해당).이건 그냥 바보같은 짓이야항상 사용하는 유일한 이유는xor al,al상위 바이트를 명시적으로 유지하는 경우입니다.

logical_or PROC                     ; x86-64 MSVC CL19
    test     cl, cl                 ; Windows ABI passes args in ecx, edx
    jne      SHORT $LN3@logical_or
    test     dl, dl
    jne      SHORT $LN3@logical_or
    xor      al, al                 ; missed peephole: xor eax,eax is strictly better
    ret      0
$LN3@logical_or:
    mov      al, 1
    ret      0
logical_or ENDP

ICC18은 또한 입력의 알려진 0/1 특성을 이용하지 않습니다. 다만,or두 입력의 비트 OR에 따라 플래그를 설정하는 지침setcc0/1을 생성합니다.

logical_or(bool, bool):             # ICC18
    xor       eax, eax                                      #4.42
    movzx     edi, dil                                      #4.33
    movzx     esi, sil                                      #4.33
    or        edi, esi                                      #4.42
    setne     al                                            #4.42
    ret                                                     #4.42

ICC는 다음 기간에도 동일한 코드를 내보냅니다.bool bitwise_or(bool a, bool b) { return a|b; }...을 촉진합니다.int(와 함께)movzx, 및 사용or비트 OR에 따라 플래그를 설정합니다.이건 정말 바보같은 짓이에요or dil,sil/setne al.

위해서bitwise_or, MSVC는, 단지,or지시(이후)movzx각 입력에 대해) 하지만 어쨌든 재평가되지 않습니다.


현재 gcc/clang에서 누락된 최적화:

ICC/MSVC만이 위의 간단한 함수로 덤 코드를 만들었지만, 이 함수는 여전히 gcc와 clang 문제를 일으킵니다.

int select(bool a, bool b, int x, int y) {
    return (a&&b) ? x : y;
}

Godbolt 컴파일러 탐색기의 Source+asm(동일한 소스, 이전 컴파일러와 다른 컴파일러를 선택).

매우 심플해 보입니다.스마트 컴파일러라면 1개의 컴파일러로 분기 없이 실행할 수 있습니다.test/cmov. x86의test명령은 비트 AND에 따라 플래그를 설정합니다.이것은 AND 명령으로 실제로 목적지를 기재하지 않습니다.(마치cmp는 입니다.sub

# hand-written implementation that no compilers come close to making
select:
    mov     eax, edx      # retval = x
    test    edi, esi      # ZF =  ((a & b) == 0)
    cmovz   eax, ecx      # conditional move: return y if ZF is set
    ret

그러나 Godbolt 컴파일러 탐색기에서 매일 빌드하는 gcc와 clang도 각 부울을 개별적으로 검사하는 훨씬 더 복잡한 코드를 만듭니다.최적화하는 방법을 알고 있습니다.bool ab = a&&b;돌아오면ab그러나 (결과를 유지하기 위한 별도의 부울 변수를 사용하여) 그렇게 쓰는 것조차 그들을 손에 쥐게 하지 않는 코드를 만들 수 없습니다.

는 와 완전히 동등하고 크기가 작기 때문에 컴파일러가 사용하는 것입니다.

클랭의 버전은 내 손으로 쓴 것보다 훨씬 더 나빠.(발신자가 제로로 내선번호가 설정되어 있는 것에 주의해 주세요).boolargs에서 32비트까지, ABI의 비공식적인 부분으로서 좁은 정수 타입에 대해서와 같이, args와 gcc가 실장하고 있습니다만, clang만이 의존합니다).

select:  # clang 6.0 trunk 317877 nightly build on Godbolt
    test    esi, esi
    cmove   edx, ecx         # x = b ? y : x
    test    edi, edi
    cmove   edx, ecx         # x = a ? y : x
    mov     eax, edx         # return x
    ret

gcc 8.0.0 20171110은 이전 버전의 gcc와 유사한 branchy code를 야간에 만듭니다.

select(bool, bool, int, int):   # gcc 8.0.0-pre   20171110
    test    dil, dil
    mov     eax, edx          ; compiling with -mtune=intel or -mtune=haswell would keep test/jcc together for macro-fusion.
    je      .L8
    test    sil, sil
    je      .L8
    rep ret
.L8:
    mov     eax, ecx
    ret

MSVC x86-64 CL19는 매우 유사한 분기 코드를 만듭니다.Windows 호출 규칙을 대상으로 합니다.여기서 정수 arg는 rcx, rdx, r8, r9입니다.

select PROC
        test     cl, cl         ; a
        je       SHORT $LN3@select
        mov      eax, r8d       ; retval = x
        test     dl, dl         ; b
        jne      SHORT $LN4@select
$LN3@select:
        mov      eax, r9d       ; retval = y
$LN4@select:
        ret      0              ; 0 means rsp += 0 after popping the return address, not C return 0.
                                ; MSVC doesn't emit the `ret imm16` opcode here, so IDK why they put an explicit 0 as an operand.
select ENDP

ICC18은 분기 코드도 만들지만 둘 다 포함mov지점 뒤의 지시.

select(bool, bool, int, int):
        test      dil, dil                                      #8.13
        je        ..B4.4        # Prob 50%                      #8.13
        test      sil, sil                                      #8.16
        jne       ..B4.5        # Prob 50%                      #8.16
..B4.4:                         # Preds ..B4.2 ..B4.1
        mov       edx, ecx                                      #8.13
..B4.5:                         # Preds ..B4.2 ..B4.4
        mov       eax, edx                                      #8.13
        ret                                                     #8.13

를 사용하여 컴파일러를 지원하려고 합니다.

int select2(bool a, bool b, int x, int y) {
    bool ab = a&&b;
    return (ab) ? x : y;
}

MSVC가 우스꽝스러울 정도로 나쁜 코드를 만들도록 유도하고 있습니다.

;; MSVC CL19  -Ox  = full optimization
select2 PROC
    test     cl, cl
    je       SHORT $LN3@select2
    test     dl, dl
    je       SHORT $LN3@select2
    mov      al, 1              ; ab = 1

    test     al, al             ;; and then test/cmov on an immediate constant!!!
    cmovne   r9d, r8d
    mov      eax, r9d
    ret      0
$LN3@select2:
    xor      al, al            ;; ab = 0

    test     al, al            ;; and then test/cmov on another path with known-constant condition.
    cmovne   r9d, r8d
    mov      eax, r9d
    ret      0
select2 ENDP

이것은 MSVC에서만 가능합니다(또한 ICC18은 상수로 설정된 레지스터에서 test/cmov의 최적화가 누락되어 있습니다).

MSVC만큼 와 clang은 MSVC와 .그것들은 같은 코드를 만듭니다.select()이는 여전히 좋지 않지만 적어도 그들을 도우려고 노력한다고 해서 MSVC와 같은 상황이 악화되지는 않습니다.


bool 및 ICCMSVC를합니다.ICC는 ICC를 지원합니다.

★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★」| ★★★★★★★★★★★★★★★★★」& 가 있는 것 || ★★★★★★★★★★★★★★★★★」&&MSVC 'ICC' 'ICC'컴파일러와 컴파일 옵션을 조합한 독자적인 코드의 컴파일러 출력을 참조해 주세요.

int select_bitand(bool a, bool b, int x, int y) {
    return (a&b) ? x : y;
}

GCC는 아직 개별적으로 분기하고 있다test의 입력 버전의 s와 입니다.select. clang은 다른 소스 버전과 동일한 두 의 asm을 수행합니다.

MSVC는 (적어도 스탠드아론 정의에서는) 다른 모든 컴파일러를 제치고 올바르게 최적화됩니다.

select_bitand PROC            ;; MSVC
    test     cl, dl           ;; ZF =  !(a & b)
    cmovne   r9d, r8d
    mov      eax, r9d         ;; could have done the mov to eax in parallel with the test, off the critical path, but close enough.
    ret      0

은 2개의 ICC18을 합니다.movzx 0을 0으로 .bool ~ ~까지int와 같은

select_bitand:          ## ICC18
    movzx     edi, dil                                      #16.49
    movzx     esi, sil                                      #16.49
    test      edi, esi                                      #17.15
    cmovne    ecx, edx                                      #17.15
    mov       eax, ecx                                      #17.15
    ret                                                     #17.15

clang+-O3-S로 다음 내용을 컴파일했습니다.

bool andbool(bool a, bool b)
{
    return a && b;
}

bool andint(int a, int b)
{
    return a && b;
}

.s일일: :

andbool(bool, bool):                           # @andbool(bool, bool)
    andb    %sil, %dil
    movl    %edi, %eax
    retq

andint(int, int):                            # @andint(int, int)
    testl   %edi, %edi
    setne   %cl
    testl   %esi, %esi
    setne   %al
    andb    %cl, %al
    retq

확실히 덜 하고 있는 것은 불 버전이다.

내 생각에 이건 아닌 것 같아요.

우선, 이 추론은 완전히 받아들일 수 없습니다.

컴파일러가 이러한 가정을 하지 않는 이유는 변수가 초기화되지 않았거나 알 수 없는 소스에서 나온 경우 다른 값을 가질 수 있기 때문입니다.

몇 가지 코드(clang 6으로 컴파일되지만 GCC 7과 MSVC 2017은 유사한 코드를 생성합니다)를 확인합니다.

부울 또는:

bool fn(bool a, bool b) {
    return a||b;
}

0000000000000000 <fn(bool, bool)>:
   0:   40 08 f7                or     dil,sil
   3:   40 88 f8                mov    al,dil
   6:   c3                      ret    

볼 수 있듯이 .단순히 0/1 체크가 됩니다.단합니니다다or.

Bool을 int로 변환:

int fn(bool a) {
    return a;
}

0000000000000000 <fn(bool)>:
   0:   40 0f b6 c7             movzx  eax,dil
   4:   c3                      ret    

다시 말하지만, 확인은 안되고, 간단한 움직임이야.

char를 bool로 변환:

bool fn(char a) {
    return a;
}

0000000000000000 <fn(char)>:
   0:   40 84 ff                test   dil,dil
   3:   0f 95 c0                setne  al
   6:   c3                      ret    

여기서 char가 0인지 아닌지를 확인하고 그에 따라 bool 값을 0 또는 1로 설정합니다.

따라서 컴파일러는 항상 0/1을 포함하는 방식으로 bool을 사용하는 것이 안전하다고 생각합니다.유효성을 확인하지 않습니다.

효율에 대해서:Bool이 최적인 것 같아.이 접근방식이 최적이 아닌 경우는 char->bool 변환뿐입니다.bool 값이 0/1로 제한되지 않는다면 이 작업은 단순한 이동일 수 있습니다.다른 모든 작업에 대해서도 현재 접근 방식은 동등하게 양호하거나 더 우수합니다.


편집: 피터 코데스가 ABI를 언급했습니다.다음은 AMD64용 System V ABI의 관련 텍스트입니다(i386의 텍스트도 비슷합니다).

부울란은 메모리 개체에 저장될 때 값이 항상 0(false) 또는 1(true)인 단일 바이트 개체로 저장됩니다.정수 레지스터에 저장되는 경우(인수로 전달되는 경우를 제외하고) 레지스터의 8바이트는 모두 중요합니다.0 이외의 값은 true로 간주됩니다.

ABI에 에서는 SysV ABI에 bool0/1로 하다.

도 MSVC에 대해서는 아무것도 수 .bool.

언급URL : https://stackoverflow.com/questions/47243955/boolean-values-as-8-bit-in-compilers-are-operations-on-them-inefficient

반응형