다음 명령어는 모두 동일한 작업 %eax
을 수행합니다. 0으로 설정 합니다. 어떤 방법이 최적입니까 (가장 적은 기계 사이클 필요)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
다음 명령어는 모두 동일한 작업 %eax
을 수행합니다. 0으로 설정 합니다. 어떤 방법이 최적입니까 (가장 적은 기계 사이클 필요)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
답변:
TL; DR 요약 : xor same, same
는 IS 모든 CPU를위한 최선의 선택 . 다른 어떤 방법도 그에 비해 장점이 없으며 다른 방법에 비해 적어도 어느 정도 장점이 있습니다. Intel 및 AMD에서 공식적으로 권장하며 컴파일러가 수행하는 작업입니다. 64 비트 모드에서는 32 비트 reg를 작성하면 상위 32가 0이xor r32, r32
되기 때문에을 계속 사용 합니다. xor r64, r64
REX 접두사가 필요하기 때문에 바이트 낭비입니다.
그보다 더 나쁜 것은 Silvermont가 xor r32,r32
64 비트 피연산자 크기가 아닌 dep-breaking으로 만 인식한다는 것입니다 . 따라서 당신이 r8..r15 사용을 제로화하고 있기 때문에 심지어 REX 접두사가 여전히 필요한 경우 xor r10d,r10d
,하지xor r10,r10
.
GP 정수 예 :
xor eax, eax ; RAX = 0. Including AL=0 etc.
xor r10d, r10d ; R10 = 0
xor edx, edx ; RDX = 0
; small code-size alternative: cdq ; zero RDX if EAX is already zero
; SUB-OPTIMAL
xor rax,rax ; waste of a REX prefix, and extra slow on Silvermont
xor r10,r10 ; bad on Silvermont (not dep breaking), same as r10d everywhere else because a REX prefix is still needed for r10d or r10.
mov eax, 0 ; doesn't touch FLAGS, but not faster and takes more bytes
and eax, 0 ; false dependency. (Microbenchmark experiments might want this)
sub eax, eax ; same as xor on most but not all CPUs; bad on Silvermont for example.
xor al, al ; false dep on some CPUs, not a zeroing idiom. Use xor eax,eax
mov al, 0 ; only 2 bytes, and probably better than xor al,al *if* you need to leave the rest of EAX/RAX unmodified
벡터 레지스터를 제로화하는 것은 일반적으로 pxor xmm, xmm
. 일반적으로 gcc가 수행하는 작업입니다 (FP 명령어와 함께 사용하기 전에도).
xorps xmm, xmm
이해할 수 있습니다. 그것은 이상의 바이트 짧은이다 pxor
,하지만 xorps
반면, 인텔 네 할렘에 실행 포트 (5)을 필요로 pxor
모든 포트 (0/1/5)에서 실행할 수 있습니다. (Nehalem의 정수와 FP 사이의 2c 우회 지연 대기 시간은 일반적으로 관련이 없습니다. 왜냐하면 순서가 맞지 않는 실행은 일반적으로 새 종속성 체인의 시작에서이를 숨길 수 있기 때문입니다).
SnB 제품군 마이크로 아키텍처에서는 xor-zeroing의 어느 쪽도 실행 포트가 필요하지 않습니다. AMD에, 및 P6 / 코어 2 인텔, - 네 할렘 사전 xorps
및 pxor
(벡터 정수의 지시로) 같은 방식으로 처리됩니다.
128b 벡터 명령어의 AVX 버전을 사용하면 reg의 위쪽 부분도 0이되므로 vpxor xmm, xmm, xmm
YMM (AVX1 / AVX2) 또는 ZMM (AVX512) 또는 향후 벡터 확장을 0으로 만드는 데 적합합니다. vpxor ymm, ymm, ymm
인코딩하는 데 추가 바이트가 필요하지 않으며 Intel에서 동일하게 실행되지만 Zen2 이전의 AMD에서는 더 느립니다 (2 uops). AVX512 ZMM 제로화에는 추가 바이트 (EVEX 접두사 용)가 필요하므로 XMM 또는 YMM 제로화가 선호됩니다.
XMM / YMM / ZMM 예
# Good:
xorps xmm0, xmm0 ; smallest code size (for non-AVX)
pxor xmm0, xmm0 ; costs an extra byte, runs on any port on Nehalem.
xorps xmm15, xmm15 ; Needs a REX prefix but that's unavoidable if you need to use high registers without AVX. Code-size is the only penalty.
# Good with AVX:
vpxor xmm0, xmm0, xmm0 ; zeros X/Y/ZMM0
vpxor xmm15, xmm0, xmm0 ; zeros X/Y/ZMM15, still only 2-byte VEX prefix
#sub-optimal AVX
vpxor xmm15, xmm15, xmm15 ; 3-byte VEX prefix because of high source reg
vpxor ymm0, ymm0, ymm0 ; decodes to 2 uops on AMD before Zen2
# Good with AVX512
vpxor xmm15, xmm0, xmm0 ; zero ZMM15 using an AVX1-encoded instruction (2-byte VEX prefix).
vpxord xmm30, xmm30, xmm30 ; EVEX is unavoidable when zeroing zmm16..31, but still prefer XMM or YMM for fewer uops on probable future AMD. May be worth using only high regs to avoid needing vzeroupper in short functions.
# Good with AVX512 *without* AVX512VL (e.g. KNL / Xeon Phi)
vpxord zmm30, zmm30, zmm30 ; Without AVX512VL you have to use a 512-bit instruction.
# sub-optimal with AVX512 (even without AVX512VL)
vpxord zmm0, zmm0, zmm0 ; EVEX prefix (4 bytes), and a 512-bit uop. Use AVX1 vpxor xmm0, xmm0, xmm0 even on KNL to save code size.
참조 되어-vxorps 제로 빠른 YMM보다 XMM 레지스터와 AMD 재규어 / 불도저 / 선에? 및
단일 또는 기사 상륙에 대한 몇 가지 ZMM 등록을 취소 할 수있는 가장 효율적인 방법은 무엇입니까?
세미 관련 : 빠른 방법 모두 ONE __m256 비트 값을 설정 하고
(1) CPU 레지스터에 설정된 모든 비트를 효율적으로 또한 AVX512 커버 k0..7
마스크 레지스터. SSE / AVX vpcmpeqd
는 많은 부분에서 dep-breaking을하고 있지만 (1s를 쓰기 위해서는 여전히 uop가 필요하지만) vpternlogd
ZMM regs 용 AVX512 는 dep-breaking도 아닙니다. 루프 내에서 ALU uop, 특히 AVX512를 사용하여 다시 만드는 대신 다른 레지스터에서 복사하는 것을 고려하십시오.
그러나 제로화는 저렴합니다. 루프 내에서 xmm reg를 xor-zeroing하는 것은 일반적으로 복사만큼 좋습니다. 단, 일부 AMD CPU (Bulldozer 및 Zen)는 벡터 reg에 대한 mov 제거 기능이 있지만 xor에 대해 0을 쓰기 위해 여전히 ALU uop이 필요합니다. -제로.
일부 CPU는 sub same,same
같은 제로화 관용구로 인식 xor
하지만 제로화 관용구 를 인식하는 모든 CPU는 인식합니다xor
. 사용 xor
하면 어떤 CPU가 어떤 제로화 관용구를 인식하는지 걱정할 필요가 없습니다.
xor
(와는 달리 인식되는 제로화 관용구 mov reg, 0
)에는 몇 가지 분명하고 미묘한 장점이 있습니다 (요약 목록, 그런 다음 확장하겠습니다).
mov reg,0
. (모든 CPU)더 작은 기계 코드 크기 (5 대신 2 바이트)는 항상 이점입니다. 코드 밀도가 높을수록 명령어 캐시 미스가 줄어들고 명령어 가져 오기가 향상되고 잠재적으로 대역폭을 디코딩 할 수 있습니다.
Intel SnB 제품군 마이크로 아키텍처에서 xor 용 실행 장치 를 사용하지 않는 이점 은 사소하지만 전력을 절약합니다. 3 개의 ALU 실행 포트만있는 SnB 또는 IvB에서 더 중요합니다. Haswell 및 이후 버전에는를 포함하여 정수 ALU 명령어를 처리 할 수있는 4 개의 실행 포트가 mov r32, imm32
있으므로 스케줄러의 완벽한 의사 결정 (실제로 항상 발생하지는 않음)을 통해 HSW는 모두 ALU가 필요한 경우에도 클럭 당 4uops를 유지할 수 있습니다. 실행 포트.
자세한 내용 은 제로화 레지스터에 대한 다른 질문에 대한 내 대답을 참조하십시오.
Michael Petch가 연결 한 Bruce Dawson의 블로그 게시물 (질문에 대한 의견에서) xor
은 실행 단위 (퓨즈되지 않은 도메인에서 0 uop)없이 레지스터 이름 변경 단계에서 처리되지만 여전히 하나의 uop이라는 사실을 놓쳤습니다. 융합 된 도메인에서. 최신 Intel CPU는 클럭 당 4 개의 융합 도메인 uop를 발행 및 폐기 할 수 있습니다. 이것이 클럭 제한 당 4 개의 0이 나오는 곳입니다. 레지스터 이름 바꾸기 하드웨어의 복잡성의 증가는 단지 4. 디자인의 폭을 제한하는 이유 중 하나 (브루스는 자신에 시리즈처럼, 아주 우수한 블로그 게시물을 작성했습니다입니다 FP 수학 및 x87 / SSE / 반올림 문제를 내가, 매우 추천하는).
AMD Bulldozer 제품군 CPU 에서 mov immediate
.NET과 동일한 EX0 / EX1 정수 실행 포트에서 실행됩니다 xor
. mov reg,reg
AGU0 / 1에서도 실행할 수 있지만 레지스터 복사에만 해당되며 즉시 설정에는 적용되지 않습니다. AMD에 따라서 AFAIK, 유일한 장점 xor
이상은 mov
짧은 인코딩입니다. 물리적 레지스터 리소스를 절약 할 수도 있지만 테스트를 보지 못했습니다.
인식 된 제로화 관용구 는 전체 레지스터 (P6 및 SnB 제품군)와 별도로 부분 레지스터 이름을 바꾸는 Intel CPU에서 부분 레지스터 페널티 를 방지 합니다.
xor
것입니다 상단 부분이 제로 것으로 레지스터에 태그 그래서, xor eax, eax
/ inc al
/ inc eax
사전 IVB CPU가 가지고있는 일반적인 부분 레지스터 처벌을 피할 수 있습니다. 를 사용하지 않아도 xor
IvB는 높은 8 비트 ( AH
)가 수정 된 다음 전체 레지스터를 읽을 때 병합 uop 만 필요하며 Haswell은이를 제거합니다.
Agner Fog의 마이크로 아치 가이드, 98 페이지 (Pentium M 섹션, SnB를 포함한 이후 섹션에서 참조) :
프로세서는 레지스터의 XOR을 0으로 설정하는 것으로 인식합니다. 레지스터의 특수 태그는 레지스터의 상위 부분이 0이므로 EAX = AL임을 기억합니다. 이 태그는 루프에서도 기억됩니다.
; Example 7.9. Partial register problem avoided in loop xor eax, eax mov ecx, 100 LL: mov al, [esi] mov [edi], eax ; No extra uop inc esi add edi, 4 dec ecx jnz LL
(pg82에서) : 프로세서는 인터럽트, 잘못된 예측 또는 기타 직렬화 이벤트가 발생하지 않는 한 EAX의 상위 24 비트가 0이라는 것을 기억합니다.
또한 가이드 확인한다의 pg82 mov reg, 0
되어 있지 초 P6에 적어도, 영점 조정 관용구로 인식은 PIII 또는 PM과 같은 디자인. 나중에 CPU에서 감지하는 데 트랜지스터를 사용하면 매우 놀랍습니다.
xor
조건을 테스트 할 때주의해야 함을 의미하는 플래그를 설정 합니다. setcc
안타깝게도는 8 비트 대상에서만 사용할 수 있으므로 일반적으로 부분 등록 페널티를 피하기 위해주의해야합니다.
x86-64가 16/32/64 비트에 대해 제거 된 opcode 중 하나 (예 : AAM)를 재활용 setcc r/m
하고, 술어가 r / m 필드의 소스 레지스터 3 비트 필드에 인코딩 된 경우 (방법 일부 다른 단일 피연산자 명령어는이를 opcode 비트로 사용합니다. 그러나 그들은 그렇게하지 않았고 어쨌든 x86-32에는 도움이되지 않았습니다.
이상적으로는 xor
/ 플래그 설정 / setcc
/ 전체 레지스터 를 사용해야합니다 .
...
call some_func
xor ecx,ecx ; zero *before* the test
test eax,eax
setnz cl ; cl = (some_func() != 0)
add ebx, ecx ; no partial-register penalty here
이는 모든 CPU에서 최적의 성능을 제공합니다 (지속, uop 병합 또는 잘못된 종속성 없음).
플래그 설정 명령어 전에 xor를 원하지 않을 때 상황이 더 복잡해집니다 . 예를 들어 한 조건에서 분기 한 다음 동일한 플래그에서 다른 조건에 대해 setcc를 설정하려고합니다. 예를 들어 cmp/jle
,, sete
그리고 여분의 레지스터가 없거나 xor
가져 가지 않은 코드 경로를 완전히 차단하려고 합니다.
플래그에 영향을주지 않는 인식 된 제로화 관용구가 없으므로 최상의 선택은 대상 마이크로 아키텍처에 따라 다릅니다. Core2에서 병합 uop를 삽입하면 2 또는 3주기 지연이 발생할 수 있습니다. SnB에서는 더 저렴 해 보이지만 측정하는 데 많은 시간을 소비하지 않았습니다. mov reg, 0
/를 사용하면 setcc
구형 Intel CPU에서는 상당한 불이익이 발생하며 최신 Intel에서는 여전히 다소 나빠집니다.
플래그 설정 명령보다 먼저 xor-zero를 수행 할 수없는 경우 setcc
/를 사용 movzx r32, r8
하는 것이 Intel P6 및 SnB 제품군에 가장 적합한 대안 일 것입니다. xor-zeroing 후에 테스트를 반복하는 것보다 낫습니다. ( sahf
/ lahf
또는 pushf
/ 조차 고려하지 마십시오 popf
). IvB는 제거 할 수 있습니다 movzx r32, r8
(예 : xor-zeroing과 같이 실행 단위 나 지연없이 레지스터 이름 변경으로 처리). Haswell 이상에서는 일반 mov
명령 만 제거 하므로 movzx
실행 단위를 사용하고 지연 시간이 0이 아니므로 test / setcc
/ movzx
가 xor
/ test / 보다 나빠지 setcc
지만 적어도 test / mov r,0
/ 만큼은 좋지만 setcc
이전 CPU에서는 훨씬 좋습니다.
먼저 제로화없이 setcc
/ movzx
를 사용 하는 것은 하위 레지스터에 대해 deps를 별도로 추적하지 않기 때문에 AMD / P4 / Silvermont에서 좋지 않습니다. 레지스터의 이전 값에 잘못된 dep가 있습니다. 사용 mov reg, 0
/ setcc
/ 종속성 파괴를 제로화를 위해 아마 가장 좋은 대안이다 xor
/ 테스트 / setcc
옵션이 아닙니다.
물론 setcc
의 출력이 8 비트보다 더 넓을 필요가 없다면 아무것도 0으로 만들 필요가 없습니다. 그러나 최근에 긴 종속성 체인의 일부였던 레지스터를 선택하는 경우 P6 / SnB 이외의 CPU에 대한 잘못된 종속성에주의하십시오. (그리고 일부 사용중인 레지스터를 저장 / 복원 할 수있는 함수를 호출하는 경우 부분적인 등록 중단 또는 추가 uop가 발생하지 않도록주의하십시오.)
and
즉치 0 은 내가 아는 CPU의 이전 값과 독립적으로 특수한 경우가 아니므로 종속성 체인을 끊지 않습니다. 그것은 장점이없고 xor
많은 단점이 있습니다.
당신이 경우에만 microbenchmarks 작성을위한 유용한 원하는 대기 시간 테스트의 일환으로 종속성을하지만, 영점 조정 및 추가하여 알려진 값을 만들려고합니다.
종속성 중단으로 인식되는 제로화 관용구 (예 : sub same,same
일부 CPU에 있지만 모든 CPU에서 인식됨)를 포함하여 마이크로 아치 세부 사항 은 http://agner.org/optimize/ 를 참조하십시오xor same,same
. mov
이전 값에 대한 종속성 체인이 끊어집니다 . 레지스터의 (소스 값에 관계없이 0이든 아니든, 그것이 mov
작동 하는 방식 이기 때문입니다 ). xor
src와 dest가 동일한 레지스터 인 특수한 경우에만 종속성 체인을 끊기 때문에 특별히 인식 된 종속성 차단기 mov
목록에서 제외 됩니다. (또한 다른 이점과 함께 제로화 관용구로 인식되지 않기 때문입니다.)
흥미롭게도, 오래된 P6 디자인 (펜티엄 III 내지 PPro)는 않았다 인식 xor
일부만 레지스터 노점을 피할 목적으로 영점 관용구 같은 의존성 차단기로 -zeroing 사용 때문에 경우에 따라서는 가치가, 모두 mov
다음과 xor
dep를 깨기 위해 -zeroing 한 다음 다시 0으로 설정 + 상위 비트가 0이되도록 내부 태그 비트를 설정하므로 EAX = AX = AL입니다.
Agner Fog의 예제 6.17을 참조하십시오. 그의 microarch pdf에서. 그는 이것이 P2, P3, 심지어 (초기?) PM에도 적용된다고 말합니다. 링크 된 블로그 게시물에 대한 댓글에 따르면 이러한 감독은 PPro 뿐이지 만 Katmai PIII에서 테스트했고 @Fanael은 Pentium M에서 테스트했으며 우리는 둘 다 지연 시간에 대한 종속성을 깨지 않는다는 것을 발견했습니다. 바운드 imul
체인. 이것은 불행히도 Agner Fog의 결과를 확인합니다.
실제로 코드를 더 멋지게 만들거나 지침을 저장 mov
한다면 코드 크기 이외의 성능 문제가 발생하지 않는 한 플래그를 건드리지 않도록 0을 사용하십시오 . 뭉개지는 플래그를 피하는 것이를 사용하지 않는 유일한 현명한 이유 xor
이지만 때로는 예비 레지스터가있는 경우 플래그를 설정하는 것보다 먼저 xor-zero 할 수 있습니다.
mov
-zero before setcc
는 지연 시간이 movzx reg32, reg8
이후 보다 더 좋지만 (다른 레지스터를 선택할 수있는 Intel 제외) 코드 크기가 더 나쁩니다.
mov reg, src
OO CPU에 대한 dep 체인을 끊습니다 (src가 imm32 [mem]
, 또는 다른 레지스터 임에 관계없이 ). 이 종속성 차단은 src와 dest가 동일한 레지스터 일 때만 발생하는 특별한 경우가 아니기 때문에 최적화 매뉴얼에서 언급되지 않습니다. 그것은 항상 자신의 이명 령에 의존하지 않는 지침을 발생합니다. (인텔 popcnt/lzcnt/tzcnt
이 목적지에 거짓 출발을 구현 한 경우 제외 )
mov
무료가 아니라 지연 시간이 0입니다. "실행 포트를 사용하지 않음"부분은 일반적으로 중요하지 않습니다. 융합 도메인 처리량은 쉽게 병목 현상이 될 수 있습니다. 믹스에 부하 또는 상점이 있습니다.
xor r64, r64
바이트를 낭비하지 않습니다. 당신이 말했듯 xor r32, r32
이 특히 KNL에서 최선의 선택입니다. 자세한 내용은이 micrarch 매뉴얼의 15.7 "특별한 독립 사례"섹션을 참조하십시오.