항상 그렇듯이 주변 코드 컨텍스트에 따라 다릅니다 . 예를 들어 x<<1
배열 인덱스로 사용 하고 있습니까? 아니면 다른 것에 추가 하시겠습니까? 두 경우 모두 작은 시프트 카운트 (1 또는 2)는 컴파일러가 시프트 만하면되는 경우보다 더 많이 최적화 할 수 있습니다 . 전체 처리량 대 대기 시간 대 프런트 엔드 병목 현상의 절충은 말할 것도 없습니다. 작은 조각의 성능은 1 차원이 아닙니다.
하드웨어 시프트 명령어는 컴파일을위한 컴파일러의 유일한 옵션이 x<<1
아니지만 다른 답변은 대부분이를 가정합니다.
x << 1
x+x
부호없는 정수와 2의 보수 부호있는 정수와 정확히 동일합니다 . 컴파일러는 컴파일하는 동안 대상 하드웨어를 항상 알고 있으므로 이와 같은 트릭을 활용할 수 있습니다.
에 인텔 하 스웰 , add
시계 처리량 당 4를 가지고 있지만 shl
즉각적인 카운트 만 2 클럭 처리량 당이있다. ( http://agner.org/optimize/ 참조 지침 표 및 기타 링크는 를 .x86태그 위키). SIMD 벡터 시프트는 클럭 당 1 (Skylake에서 2)이지만 SIMD 벡터 정수 추가는 클럭 당 2 (Skylake에서 3)입니다. 대기 시간은 동일하지만 1주기입니다.
또한 shl
opcode에서 카운트가 암시되는 위치 에 대한 특별한 시프트 바이 1 인코딩이 있습니다. 8086에는 즉시 카운트 시프트가 없었으며 cl
레지스터 별로 하나씩 만 있습니다. 메모리 피연산자를 시프트하지 않는 한 왼쪽 시프트 만 추가 할 수 있기 때문에 이는 대부분 오른쪽 시프트와 관련이 있습니다. 그러나 나중에 값이 필요하면 레지스터에 먼저로드하는 것이 좋습니다. 그러나 어쨌든 shl eax,1
또는 add eax,eax
보다 1 바이트 더 짧으며 shl eax,10
코드 크기는 직접 (디코딩 / 프런트 엔드 병목 현상) 또는 간접적으로 (L1I 코드 캐시 누락) 성능에 영향을 미칠 수 있습니다.
보다 일반적으로, 작은 시프트 카운트는 x86의 주소 지정 모드에서 스케일링 된 인덱스로 최적화 될 수 있습니다. 요즘 일반적으로 사용되는 대부분의 다른 아키텍처는 RISC이며 확장 인덱스 주소 지정 모드가 없지만 x86은 언급 할 가치가있는 일반적인 아키텍처입니다. (예를 들어 4 바이트 요소의 배열을 인덱싱하는 경우)에 대한 배율 인수를 1 씩 늘릴 여지가 있습니다 int arr[]; arr[x<<1]
.
의 원래 값 x
이 여전히 필요한 상황에서는 복사 + 이동이 필요합니다. 그러나 대부분의 x86 정수 명령어는 제자리에서 작동합니다. (대상은 add
또는 같은 명령어의 소스 중 하나입니다 shl
.) x86-64 System V 호출 규칙은 레지스터에 인수를 전달합니다. 첫 번째 인수는에 edi
있고 반환 값은 eax
에 있습니다. 따라서 반환하는 함수 x<<10
는 컴파일러가 복사 + 시프트를 방출 하도록합니다 암호.
이 LEA
명령어를 사용하면 시프트 및 추가를 수행 할 수 있습니다 (어드레싱 모드 머신 인코딩을 사용하기 때문에 시프트 카운트 0에서 3). 결과를 별도의 레지스터에 넣습니다.
gcc와 clang은 모두 Godbolt 컴파일러 탐색기에서 볼 수 있듯이 동일한 방식으로 이러한 함수를 최적화합니다 .
int shl1(int x) { return x<<1; }
lea eax, [rdi+rdi] # 1 cycle latency, 1 uop
ret
int shl2(int x) { return x<<2; }
lea eax, [4*rdi] # longer encoding: needs a disp32 of 0 because there's no base register, only scaled-index.
ret
int times5(int x) { return x * 5; }
lea eax, [rdi + 4*rdi]
ret
int shl10(int x) { return x<<10; }
mov eax, edi # 1 uop, 0 or 1 cycle latency
shl eax, 10 # 1 uop, 1 cycle latency
ret
구성 요소가 2 개인 LEA는 최신 Intel 및 AMD CPU에서 1주기 지연 시간과 클럭 당 2 개의 처리량을 제공합니다. (Sandybridge 가족 및 Bulldozer / Ryzen). Intel에서는 클럭 처리량 당 1 개이며 lea eax, [rdi + rsi + 123]
. (관련 : 왜이 C ++ 코드가 Collatz 추측을 테스트하기 위해 손으로 작성한 어셈블리보다 빠른가요? 자세히 설명합니다.)
어쨌든 10 씩 복사 + 시프트는 별도의 mov
명령이 필요합니다 . 최근 많은 CPU에서 지연 시간이 0 일 수 있지만 여전히 프런트 엔드 대역폭과 코드 크기가 필요합니다. ( x86의 MOV가 정말 "무료"일 수 있습니까? 왜 이것을 전혀 재현 할 수 없습니까? )
또한 관련이 있습니다. x86에서 2 개의 연속적인 leal 명령어 만 사용하여 레지스터에 37을 곱하는 방법은 무엇입니까? .
컴파일러는 또한 주변 코드를 자유롭게 변환 할 수 있으므로 실제 이동이 발생하지 않거나 다른 작업과 결합됩니다 .
예를 들어 if(x<<1) { }
를 사용하여 and
상위 비트를 제외한 모든 비트를 확인할 수 있습니다 . x86에서는 . 대신 / test
와 같은 명령어를 사용합니다 . 이 최적화는 모든 시프트 수에 대해 작동하며, 많은 수의 시프트가 느리거나 (예 : Pentium 4) 존재하지 않는 (일부 마이크로 컨트롤러) 머신에서도 작동합니다.test eax, 0x7fffffff
jz .false
shl eax,1 / jz
많은 ISA에는 단순한 이동 이상의 비트 조작 명령이 있습니다. 예를 들어 PowerPC에는 많은 비트 필드 추출 / 삽입 명령이 있습니다. 또는 ARM에는 다른 명령어의 일부로 소스 피연산자의 시프트가 있습니다. (따라서 이동 / 회전 명령은 move
이동 된 소스를 사용하는 의 특수한 형태 일뿐 입니다.)
기억 C 어셈블리 언어가 아닙니다 . 효율적으로 컴파일하기 위해 소스 코드를 조정할 때는 항상 최적화 된 컴파일러 출력을 확인하십시오.