컴파일러에서 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
'source' 카테고리의 다른 글
| Vuetify 필드에 비밀번호 문자 숨기기 (0) | 2022.07.23 |
|---|---|
| 구조 할당 또는 memcpy? (0) | 2022.07.23 |
| Java Key Store에 PEM Import (0) | 2022.07.23 |
| VueJ - 빌드 후 여러 js 파일이 dist에 있음 (0) | 2022.07.23 |
| null Boolean이 true인지 여부를 확인합니다. 결과는 예외입니다. (0) | 2022.07.23 |