x << 1 또는 x << 10 중 어느 것이 더 빠릅니까?
나는 아무것도 최적화하고 싶지 않습니다. 맹세합니다. 호기심에서이 질문을하고 싶습니다. 나는 대부분의 하드웨어에서 비트 변화 (예를 들면의 어셈블리 명령어가 있다는 것을 알고 shl
, shr
하나의 명령이다). 그러나 얼마나 많은 비트를 이동하는지가 중요합니까 (나노초 단위 또는 CPU 전술 단위). 즉, 다음 중 하나가 CPU에서 더 빠릅니까?
x << 1;
과
x << 10;
그리고이 질문에 대해 나를 미워하지 마십시오. :)
잠재적으로 CPU에 따라 다릅니다.
그러나 모든 최신 CPU (x86, ARM)는 "배럴 시프터"(일정한 시간에 임의의 시프트를 수행하도록 특별히 설계된 하드웨어 모듈)를 사용합니다.
그래서 결론은 ... 아뇨. 차이 없음.
일부 임베디드 프로세서에는 "하나씩 이동"명령 만 있습니다. 같은 프로세서에서 컴파일러는 바꿀 것 x << 3
으로 ((x << 1) << 1) << 1
.
모토로라 MC68HCxx는 이러한 한계가있는 가장 인기있는 제품군 중 하나라고 생각합니다. 다행히도 이러한 아키텍처는 현재 매우 드물며 대부분은 가변 시프트 크기의 배럴 시프터를 포함합니다.
최신 파생 제품이 많은 Intel 8051은 임의의 비트 수를 이동할 수 없습니다.
이것에 대한 많은 경우가 있습니다.
많은 고속 MPU에는 배럴 시프터, 멀티플렉서와 같은 전자 회로가있어 일정한 시간에 모든 시프트를 수행합니다.
MPU에 1 비트 시프트 만있는 경우
x << 10
일반적으로 10 시프트 또는 2 시프트로 바이트 복사로 수행되므로 일반적으로 더 느립니다.그러나 일반적인 경우가 알려져
x << 10
도 될 것 빠른 이상을x << 1
. x가 16 비트 인 경우 하위 6 비트 만주의해야합니다 (다른 모든 비트는 시프트 됨). 따라서 MPU는 하위 바이트 만로드하면되므로 8 비트 메모리에 대한 단일 액세스 주기만 만들고x << 10
2 개의 액세스주기가 필요합니다. 액세스주기가 시프트보다 느리면 (하위 바이트 지우기)x << 10
더 빠릅니다. 이것은 느린 외부 데이터 RAM에 액세스하는 동안 빠른 온보드 프로그램 ROM이있는 마이크로 컨트롤러에 적용될 수 있습니다.경우 3 외에도 컴파일러는
x << 10
16x16 곱셈을 16x8 1로 바꾸는 것과 같이 (하위 바이트는 항상 0이므로) 더 낮은 너비의 비트로 추가 작업을 최적화 할 수 있습니다.
일부 마이크로 컨트롤러에는 왼쪽 시프트 명령이 전혀 없으며 add x,x
대신 사용 합니다.
ARM에서는 다른 명령어의 부작용으로이 작업을 수행 할 수 있습니다. 따라서 잠재적으로 둘 중 하나에 대해 대기 시간이 전혀 없습니다.
여기 내가 좋아하는 CPU 하는, x<<2
만큼 배 걸립니다 x<<1
:)
이는 CPU와 컴파일러에 따라 다릅니다. 기본 CPU에 배럴 시프터를 사용하여 임의의 비트 시프트가 있더라도 컴파일러가 해당 리소스를 활용하는 경우에만 발생합니다.
데이터의 비트 단위로 너비를 벗어난 모든 것을 이동하는 것은 C 및 C ++에서 "정의되지 않은 동작"입니다. 서명 된 데이터의 오른쪽 시프트도 "구현 정의"입니다. 속도에 대해 너무 많은 관심을 갖기보다는 다른 구현에 대해 동일한 답을 얻고 있다는 점을 염려하십시오.
ANSI C 섹션 3.3.7에서 인용 :
3.3.7 비트 시프트 연산자
통사론
shift-expression: additive-expression shift-expression << additive-expression shift-expression >> additive-expression
제약
각 피연산자는 정수 유형을 가져야합니다.
의미론
적분 프로모션은 각 피연산자에서 수행됩니다. 결과 유형은 승격 된 왼쪽 피연산자의 유형입니다. 오른쪽 피연산자의 값이 음수이거나 승격 된 왼쪽 피연산자의 너비 (비트)보다 크거나 같으면 동작이 정의되지 않습니다.
E1 << E2의 결과는 E1 왼쪽으로 이동 한 E2 비트 위치입니다. 비워진 비트는 0으로 채워집니다. E1에 부호없는 유형이있는 경우 결과 값은 E1에 수량을 곱하고 2를 E2 거듭 제곱하고 E1에 부호없는 long 유형이 있으면 모듈로 ULONG_MAX + 1이 감소하고 그렇지 않으면 UINT_MAX + 1이됩니다. (상수 ULONG_MAX 및 UINT_MAX는 헤더에 정의되어 있습니다.)
E1 >> E2의 결과는 E1 오른쪽으로 이동 한 E2 비트 위치입니다. E1에 부호없는 유형이 있거나 E1에 부호있는 유형과 음이 아닌 값이있는 경우 결과 값은 E1 몫의 정수 부분을 수량으로 나눈 값 2를 E2 제곱합니다. E1에 부호있는 유형과 음수 값이있는 경우 결과 값은 구현에서 정의됩니다.
그래서:
x = y << z;
"<<": y × 2 z ( 오버플로가 발생하면 정의되지 않음 );
x = y >> z;
">>": 부호에 대한 구현 정의 (대부분 산술 시프트의 결과 : y / 2 z ).
8 비트 프로세서에서는 x<<1
실제로 16 비트 값 보다 훨씬 느릴 수 있습니다 x<<10
.
For example a reasonable translation of x<<1
may be:
byte1 = (byte1 << 1) | (byte2 >> 7)
byte2 = (byte2 << 1)
whereas x<<10
would be more simple:
byte1 = (byte2 << 2)
byte2 = 0
Notice how x<<1
shifts more often and even farther than x<<10
. Furthermore the result of x<<10
doesn't depend on the content of byte1. This could speed up the operation additionally.
On some generations of Intel CPUs (P2 or P3? Not AMD though, if I remember right), the bitshift operations are ridiculously slow. Bitshift by 1 bit should always be fast though since it can just use addition. Another question to consider is whether bitshifts by a constant number of bits are faster than variable-length shifts. Even if the opcodes are the same speed, on x86 the nonconstant righthand operand of a bitshift must occupy the CL register, which imposes additional constrains on register allocation and may slow the program down that way too.
As always, it depends on the surrounding code context: e.g. are you using x<<1
as an array index? Or adding it to something else? In either case, small shift counts (1 or 2) can often optimize even more than if the compiler ends up having to just shift. Not to mention the whole throughput vs. latency vs. front-end bottlenecks tradeoff. Performance of a tiny fragment is not one-dimensional.
A hardware shift instructions is not a compiler's only option for compiling x<<1
, but the other answers are mostly assuming that.
x << 1
is exactly equivalent to x+x
for unsigned, and for 2's complement signed integers. Compilers always know what hardware they're targeting while they're compiling, so they can take advantage of tricks like this.
On Intel Haswell, add
has 4 per clock throughput, but shl
with an immediate count has only 2 per clock throughput. (See http://agner.org/optimize/ for instruction tables, and other links in the x86 tag wiki). SIMD vector shifts are 1 per clock (2 in Skylake), but SIMD vector integer adds are 2 per clock (3 in Skylake). Latency is the same, though: 1 cycle.
There's also a special shift-by-one encoding of shl
where the count is implicit in the opcode. 8086 didn't have immediate-count shifts, only by-one and by cl
register. This is mostly relevant for right-shifts, because you can just add for left shifts unless you're shifting a memory operand. But if the value is needed later, it's better to load into a register first. But anyway, shl eax,1
or add eax,eax
is one byte shorter than shl eax,10
, and code-size can directly (decode / front-end bottlenecks) or indirectly (L1I code cache misses) affect performance.
More generally, small shift counts can sometimes be optimized into a scaled index in an addressing mode on x86. Most other architectures in common use these days are RISC, and don't have scaled-index addressing modes, but x86 is a common enough architecture for this to be worth mentioning. (e.g.g if you're indexing an array of 4-byte elements, there's room to increase the scale factor by 1 for int arr[]; arr[x<<1]
).
Needing to copy+shift is common in situations where the original value of x
is still needed. But most x86 integer instructions operate in-place. (The destination is one of the sources for instructions like add
or shl
.) The x86-64 System V calling convention passes args in registers, with the first arg in edi
and return value in eax
, so a function that returns x<<10
also makes the compiler emit copy+shift code.
The LEA
instruction lets you shift-and-add (with a shift count of 0 to 3, because it uses addressing-mode machine-encoding). It puts the result in a separate register.
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
LEA with 2 components has 1 cycle latency and 2-per-clock throughput on recent Intel and AMD CPUs. (Sandybridge-family and Bulldozer/Ryzen). On Intel, it's only 1 per clock throughput with 3c latency for lea eax, [rdi + rsi + 123]
. (Related: Why is this C++ code faster than my hand-written assembly for testing the Collatz conjecture? goes into this in detail.)
Anyway, copy+shift by 10 needs a separate mov
instruction. It might be zero latency on many recent CPUs, but it still takes front-end bandwidth and code size. (Can x86's MOV really be "free"? Why can't I reproduce this at all?)
Also related: How to multiply a register by 37 using only 2 consecutive leal instructions in x86?.
The compiler is also free to transform the surrounding code so there isn't an actual shift, or it's combined with other operations.
For example if(x<<1) { }
could use an and
to check all bits except the high bit. On x86, you'd use a test
instruction, like test eax, 0x7fffffff
/ jz .false
instead of shl eax,1 / jz
. This optimization works for any shift count, and it also works on machines where large-count shifts are slow (like Pentium 4), or non-existent (some micro-controllers).
Many ISAs have bit-manipulation instructions beyond just shifting. e.g. PowerPC has a lot of bit-field extract / insert instructions. Or ARM has shifts of source operands as part of any other instruction. (So shift/rotate instructions are just a special form of move
, using a shifted source.)
Remember, C is not assembly language. Always look at optimized compiler output when you're tuning your source code to compile efficiently.
참고URL : https://stackoverflow.com/questions/4234120/which-is-faster-x1-or-x10
'developer tip' 카테고리의 다른 글
Vim에서 일치하지 않는 모든 줄 숨기기 (0) | 2020.09.23 |
---|---|
GNU Emacs에서 한 줄씩 스크롤하는 방법은 무엇입니까? (0) | 2020.09.23 |
Python : tf-idf-cosine : 문서 유사성 찾기 (0) | 2020.09.23 |
UILineBreakModeWordWrap은 더 이상 사용되지 않습니다. (0) | 2020.09.23 |
주 함수에서 argc와 argv의 이름을 바꾸는 것이 안전합니까? (0) | 2020.09.23 |