Trình biên dịch thực sự tốt trong việc tối ưu hóa switch
. Gcc gần đây cũng tốt trong việc tối ưu hóa một loạt các điều kiện trong một if
.
Tôi đã thực hiện một số trường hợp thử nghiệm trên godbolt .
Khi các case
giá trị được nhóm lại gần nhau, gcc, clang và icc đều đủ thông minh để sử dụng bitmap để kiểm tra xem giá trị có phải là một trong những giá trị đặc biệt hay không.
ví dụ: gcc 5.2 -O3 biên dịch switch
thành (và if
một cái gì đó rất giống nhau):
errhandler_switch(errtype): # gcc 5.2 -O3
cmpl $32, %edi
ja .L5
movabsq $4301325442, %rax # highest set bit is bit 32 (the 33rd bit)
btq %rdi, %rax
jc .L10
.L5:
rep ret
.L10:
jmp fire_special_event()
Lưu ý rằng bitmap là dữ liệu ngay lập tức, do đó, không có bộ nhớ cache dữ liệu tiềm năng nào truy cập vào nó hoặc bảng nhảy.
gcc 4.9.2 -O3 biên dịch thành switch
bitmap, nhưng thực hiện 1U<<errNumber
với Mov / shift. Nó biên dịch if
phiên bản thành một loạt các chi nhánh.
errhandler_switch(errtype): # gcc 4.9.2 -O3
leal -1(%rdi), %ecx
cmpl $31, %ecx # cmpl $32, %edi wouldn't have to wait an extra cycle for lea's output.
# However, register read ports are limited on pre-SnB Intel
ja .L5
movl $1, %eax
salq %cl, %rax # with -march=haswell, it will use BMI's shlx to avoid moving the shift count into ecx
testl $2150662721, %eax
jne .L10
.L5:
rep ret
.L10:
jmp fire_special_event()
Lưu ý cách nó trừ 1 từ errNumber
(với lea
để kết hợp thao tác đó với di chuyển). Điều đó cho phép nó phù hợp với bitmap thành 32 bit ngay lập tức, tránh ngay lập tức 64 bitmovabsq
cần nhiều byte lệnh hơn.
Một chuỗi ngắn hơn (trong mã máy) sẽ là:
cmpl $32, %edi
ja .L5
mov $2150662721, %eax
dec %edi # movabsq and btq is fewer instructions / fewer Intel uops, but this saves several bytes
bt %edi, %eax
jc fire_special_event
.L5:
ret
(Lỗi không sử dụng jc fire_special_event
là ở khắp mọi nơi và là lỗi của trình biên dịch .)
rep ret
được sử dụng trong các mục tiêu nhánh và theo các nhánh có điều kiện, vì lợi ích của AMD K8 và K10 cũ (tiền Bulldozer): `rep ret` nghĩa là gì? . Không có nó, dự đoán nhánh không hoạt động tốt trên các CPU lỗi thời đó.
bt
(kiểm tra bit) với một thanh ghi arg là nhanh. Nó kết hợp công việc dịch chuyển trái 1 errNumber
bit và thực hiện một test
, nhưng vẫn là độ trễ 1 chu kỳ và chỉ một uop Intel duy nhất. Nó chậm với một bộ nhớ arg vì ngữ nghĩa quá giống CISC của nó: với toán hạng bộ nhớ cho "chuỗi bit", địa chỉ của byte được kiểm tra được tính dựa trên đối số khác (chia cho 8) và không không giới hạn ở đoạn 1, 2, 4 hoặc 8byte được chỉ ra bởi toán hạng bộ nhớ.
Từ các bảng hướng dẫn của Agner Fog, hướng dẫn thay đổi số đếm chậm hơn so với bt
Intel gần đây (2 u thay vì 1 và shift không làm mọi thứ khác cần thiết).