x86 어셈블리에서 레지스터를 0으로 설정하는 가장 좋은 방법은 무엇입니까? xor, mov 또는 and?


119

다음 명령어는 모두 동일한 작업 %eax을 수행합니다. 0으로 설정 합니다. 어떤 방법이 최적입니까 (가장 적은 기계 사이클 필요)?

xorl   %eax, %eax
mov    $0, %eax
andl   $0, %eax

6
이 읽어보십시오 기사
마이클 페치

답변:


222

TL; DR 요약 : xor same, same는 IS 모든 CPU를위한 최선의 선택 . 다른 어떤 방법도 그에 비해 장점이 없으며 다른 방법에 비해 적어도 어느 정도 장점이 있습니다. Intel 및 AMD에서 공식적으로 권장하며 컴파일러가 수행하는 작업입니다. 64 비트 모드에서는 32 비트 reg를 작성하면 상위 32가 0이xor r32, r32 되기 때문에을 계속 사용 합니다. xor r64, r64REX 접두사가 필요하기 때문에 바이트 낭비입니다.

그보다 더 나쁜 것은 Silvermont가 xor r32,r3264 비트 피연산자 크기가 아닌 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 인텔, - 네 할렘 사전 xorpspxor(벡터 정수의 지시로) 같은 방식으로 처리됩니다.

128b 벡터 명령어의 AVX 버전을 사용하면 reg의 위쪽 부분도 0이되므로 vpxor xmm, xmm, xmmYMM (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가 필요하지만) vpternlogdZMM regs 용 AVX512 는 dep-breaking도 아닙니다. 루프 내에서 ALU uop, 특히 AVX512를 사용하여 다시 만드는 대신 다른 레지스터에서 복사하는 것을 고려하십시오.

그러나 제로화는 저렴합니다. 루프 내에서 xmm reg를 xor-zeroing하는 것은 일반적으로 복사만큼 좋습니다. 단, 일부 AMD CPU (Bulldozer 및 Zen)는 벡터 reg에 대한 mov 제거 기능이 있지만 xor에 대해 0을 쓰기 위해 여전히 ALU uop이 필요합니다. -제로.


다양한 uarches에서 xor와 같은 관용구를 제로화하는 것의 특별한 점

일부 CPU는 sub same,same같은 제로화 관용구로 인식 xor하지만 제로화 관용구 를 인식하는 모든 CPU는 인식합니다xor . 사용 xor하면 어떤 CPU가 어떤 제로화 관용구를 인식하는지 걱정할 필요가 없습니다.

xor(와는 달리 인식되는 제로화 관용구 mov reg, 0)에는 몇 가지 분명하고 미묘한 장점이 있습니다 (요약 목록, 그런 다음 확장하겠습니다).

  • 보다 작은 코드 크기 mov reg,0. (모든 CPU)
  • 나중 코드에 대한 부분 등록 페널티를 방지합니다. (Intel P6 제품군 및 SnB 제품군).
  • 실행 단위를 사용하지 않으므로 전력을 절약하고 실행 리소스를 확보 할 수 있습니다. (Intel SnB 제품군)
  • 더 작은 uop (즉시 데이터 없음)은 필요한 경우 근처 지침을 빌릴 수 있도록 uop 캐시 라인에 공간을 남깁니다. (Intel SnB 제품군).
  • 물리 레지스터 파일의 항목을 사용하지 않습니다 . (적어도 Intel SnB 제품군 (및 P4)은 Intel P6 제품군 마이크로 아키텍처와 같이 ROB에 레지스터 상태를 유지하는 대신 유사한 PRF 설계를 사용하기 때문에 AMD도 가능합니다.)

더 작은 기계 코드 크기 (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,regAGU0 / 1에서도 실행할 수 있지만 레지스터 복사에만 해당되며 즉시 설정에는 적용되지 않습니다. AMD에 따라서 AFAIK, 유일한 장점 xor이상은 mov짧은 인코딩입니다. 물리적 레지스터 리소스를 절약 할 수도 있지만 테스트를 보지 못했습니다.


인식 된 제로화 관용구 는 전체 레지스터 (P6 및 SnB 제품군)와 별도로 부분 레지스터 이름을 바꾸는 Intel CPU에서 부분 레지스터 페널티방지 합니다.

xor것입니다 상단 부분이 제로 것으로 레지스터에 태그 그래서, xor eax, eax/ inc al/ inc eax사전 IVB CPU가 가지고있는 일반적인 부분 레지스터 처벌을 피할 수 있습니다. 를 사용하지 않아도 xorIvB는 높은 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/ movzxxor/ 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작동 하는 방식 이기 때문입니다 ). xorsrc와 dest가 동일한 레지스터 인 특수한 경우에만 종속성 체인을 끊기 때문에 특별히 인식 된 종속성 차단기 mov목록에서 제외 됩니다. (또한 다른 이점과 함께 제로화 관용구로 인식되지 않기 때문입니다.)

흥미롭게도, 오래된 P6 디자인 (펜티엄 III 내지 PPro)는 않았다 인식 xor일부만 레지스터 노점을 피할 목적으로 영점 관용구 같은 의존성 차단기로 -zeroing 사용 때문에 경우에 따라서는 가치가, 모두 mov 다음과 xordep를 깨기 위해 -zeroing 한 다음 다시 0으로 설정 + 상위 비트가 0이되도록 내부 태그 비트를 설정하므로 EAX = AX = AL입니다.

Agner Fog의 예제 6.17을 참조하십시오. 그의 microarch pdf에서. 그는 이것이 P2, P3, 심지어 (초기?) PM에도 적용된다고 말합니다. 링크 된 블로그 게시물에 대한 댓글에 따르면 이러한 감독은 PPro 뿐이지 만 Katmai PIII에서 테스트했고 @Fanael은 Pentium M에서 테스트했으며 우리는 둘 다 지연 시간에 대한 종속성을 깨지 않는다는 것을 발견했습니다. 바운드 imul체인. 이것은 불행히도 Agner Fog의 결과를 확인합니다.


TL : DR :

실제로 코드를 더 멋지게 만들거나 지침을 저장 mov한다면 코드 크기 이외의 성능 문제가 발생하지 않는 한 플래그를 건드리지 않도록 0을 사용하십시오 . 뭉개지는 플래그를 피하는 것이를 사용하지 않는 유일한 현명한 이유 xor이지만 때로는 예비 레지스터가있는 경우 플래그를 설정하는 것보다 먼저 xor-zero 할 수 있습니다.

mov-zero before setcc는 지연 시간이 movzx reg32, reg8이후 보다 더 좋지만 (다른 레지스터를 선택할 수있는 Intel 제외) 코드 크기가 더 나쁩니다.


7
대부분의 산술 명령어 OP R, S는 순서가 잘못된 CPU에 의해 레지스터 R의 내용이 레지스터 R을 타겟으로하는 이전 명령어에 의해 채워질 때까지 기다리도록 강제됩니다. 이것은 데이터 종속성입니다. 요점은 Intel / AMD 칩이 XOR R, R이 발생할 때 레지스터 R에서 데이터 종속성 을 위해 반드시 대기 해야하는 특수 하드웨어를 가지고 있으며 다른 레지스터 제로화 명령에 대해 반드시 그렇게 할 필요는 없다는 것입니다. 이것은 XOR 명령이 즉시 실행되도록 예약 될 수 있음을 의미하며, 이것이 Intel / AMD가이를 사용하도록 권장 하는 이유입니다.
Ira Baxter

3
@IraBaxter : 네, 혼동을 피하기 위해 (SO에 대한 오해를 보았 기 때문에) mov reg, srcOO CPU에 대한 dep 체인을 끊습니다 (src가 imm32 [mem], 또는 다른 레지스터 임에 관계없이 ). 이 종속성 차단은 src와 dest가 동일한 레지스터 일 때만 발생하는 특별한 경우가 아니기 때문에 최적화 매뉴얼에서 언급되지 않습니다. 그것은 항상 자신의 이명 령에 의존하지 않는 지침을 발생합니다. (인텔 popcnt/lzcnt/tzcnt이 목적지에 거짓 출발을 구현 한 경우 제외 )
Peter Cordes

2
@Zboson : 종속성이없는 명령어의 "대기 시간"은 파이프 라인에 거품이있는 경우에만 중요합니다. 이동 제거에는 좋지만 제로화 명령의 경우 지연 시간 제로 이점은 분기 오류 예측 또는 I $ 미스와 같은 경우에만 작동합니다. 여기서 실행은 데이터가 준비되기보다는 디코딩 된 명령을 기다리고 있습니다. 그러나 예, mov-elimination은 mov무료가 아니라 지연 시간이 0입니다. "실행 포트를 사용하지 않음"부분은 일반적으로 중요하지 않습니다. 융합 도메인 처리량은 쉽게 병목 현상이 될 수 있습니다. 믹스에 부하 또는 상점이 있습니다.
Peter Cordes 2015

2
Agner에 따르면 KNL은 64 비트 레지스터의 독립성을 인식하지 않습니다. 따라서 xor r64, r64바이트를 낭비하지 않습니다. 당신이 말했듯 xor r32, r32이 특히 KNL에서 최선의 선택입니다. 자세한 내용은이 micrarch 매뉴얼의 15.7 "특별한 독립 사례"섹션을 참조하십시오.
Z boson

3
아, 필요할 때 "제로 레지스터"를 사용하는 좋은 오래된 MIPS는 어디에 있습니까 ?
hayalci
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.