Tất cả các hướng dẫn sau đều thực hiện tương tự: đặt %eax
thành không. Cách nào là tối ưu (yêu cầu ít chu kỳ máy nhất)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
Tất cả các hướng dẫn sau đều thực hiện tương tự: đặt %eax
thành không. Cách nào là tối ưu (yêu cầu ít chu kỳ máy nhất)?
xorl %eax, %eax
mov $0, %eax
andl $0, %eax
Câu trả lời:
Tóm tắt TL; DR : xor same, same
là sự lựa chọn tốt nhất cho tất cả các CPU . Không có phương pháp nào khác có bất kỳ lợi thế nào hơn nó, và nó có ít nhất một số lợi thế hơn bất kỳ phương pháp nào khác. Nó chính thức được đề xuất bởi Intel và AMD, và những gì các trình biên dịch làm. Ở chế độ 64 bit, vẫn sử dụng xor r32, r32
, vì viết số 0 reg 32 ở trên 32 . xor r64, r64
lãng phí một byte, vì nó cần tiền tố REX.
Thậm chí tệ hơn thế, Silvermont chỉ nhận dạng xor r32,r32
là dep-break, không phải kích thước toán hạng 64-bit. Vì vậy, ngay cả khi tiền tố REX vẫn được yêu cầu bởi vì bạn đang sử dụng r8..r15, hãy sử dụng xor r10d,r10d
, không phảixor r10,r10
.
Ví dụ về số nguyên 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
Việc làm sạch một thanh ghi vectơ thường được thực hiện tốt nhất pxor xmm, xmm
. Đó thường là những gì gcc làm (ngay cả trước khi sử dụng với hướng dẫn FP).
xorps xmm, xmm
có thể có ý nghĩa. Nó ngắn hơn một byte pxor
, nhưng xorps
cần cổng thực thi 5 trên Intel Nehalem, trong khi pxor
có thể chạy trên bất kỳ cổng nào (0/1/5). (Độ trễ bỏ qua 2c của Nehalem giữa số nguyên và FP thường không liên quan, vì việc thực thi không theo thứ tự thường có thể ẩn nó khi bắt đầu một chuỗi phụ thuộc mới).
Trên các vi kiến trúc SnB-family, cả hương vị của xor-zeroing đều không cần cổng thực thi. Trên AMD và Intel trước Nehalem P6 / Core2, xorps
và pxor
được xử lý theo cùng một cách (như lệnh vectơ-số nguyên).
Sử dụng phiên bản AVX của lệnh vectơ 128b cũng cho số không ở phần trên của reg, do đó, vpxor xmm, xmm, xmm
là một lựa chọn tốt cho việc làm 0 YMM (AVX1 / AVX2) hoặc ZMM (AVX512) hoặc bất kỳ phần mở rộng vectơ nào trong tương lai. vpxor ymm, ymm, ymm
Tuy nhiên, không mất thêm byte để mã hóa và chạy tương tự trên Intel, nhưng chậm hơn trên AMD trước Zen2 (2 uops). Việc làm 0 ZMM của AVX512 sẽ yêu cầu thêm byte (đối với tiền tố EVEX), do đó, việc làm 0 XMM hoặc YMM nên được ưu tiên.
Ví dụ về 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.
Xem vxorps-zeroing trên AMD Jaguar / Bulldozer / Zen với thanh ghi xmm có nhanh hơn ymm không? và
Cách hiệu quả nhất để xóa một hoặc một vài đăng ký ZMM trên Knights Landing là gì?
Bán liên quan: Cách nhanh nhất để đặt giá trị __m256 thành tất cả MỘT bit và
Đặt tất cả các bit trong thanh ghi CPU thành 1 một cách hiệu quả cũng bao gồm các thanh k0..7
ghi mặt nạ AVX512 . SSE / AVX vpcmpeqd
bị phá vỡ trên nhiều (mặc dù vẫn cần một uop để viết các 1), nhưng AVX512 vpternlogd
cho các đăng ký ZMM thậm chí không bị phá vỡ. Bên trong một vòng lặp, hãy cân nhắc sao chép từ một thanh ghi khác thay vì tạo lại các thanh ghi bằng ALU uop, đặc biệt là với AVX512.
Nhưng zeroing rẻ: xor-zeroing một reg xmm bên trong một vòng lặp thường tốt như sao chép, ngoại trừ trên một số CPU AMD (Bulldozer và Zen) có loại bỏ mov cho các vector regs nhưng vẫn cần một uop ALU để ghi các số không cho xor -chính xác.
Một số CPU nhận dạng sub same,same
như một thành ngữ zeroing xor
, nhưng tất cả các CPU nhận ra bất kỳ thành ngữ zeroing nào đều nhận raxor
. Chỉ cần sử dụng xor
để bạn không phải lo lắng về việc CPU nào nhận ra thành ngữ zeroing nào.
xor
(không giống như một thành ngữ zeroing được công nhận mov reg, 0
) có một số lợi thế rõ ràng và tinh tế (danh sách tóm tắt, sau đó tôi sẽ mở rộng về những điều đó):
mov reg,0
. (Tất cả các CPU)Kích thước mã máy nhỏ hơn (2 byte thay vì 5) luôn là một lợi thế: Mật độ mã cao hơn dẫn đến việc bỏ lỡ bộ đệm ẩn lệnh ít hơn, tìm nạp lệnh tốt hơn và có khả năng giải mã băng thông.
Lợi ích của việc không sử dụng đơn vị thực thi cho xor trên vi kiến trúc Intel SnB-family là nhỏ, nhưng tiết kiệm điện năng. Nó có nhiều khả năng là vấn đề trên SnB hoặc IvB, chỉ có 3 cổng thực thi ALU. Haswell trở lên có 4 cổng thực thi có thể xử lý các lệnh ALU số nguyên, bao gồm mov r32, imm32
, vì vậy với việc đưa ra quyết định hoàn hảo bởi bộ lập lịch (điều này không phải lúc nào cũng xảy ra trong thực tế), HSW vẫn có thể duy trì 4 uops mỗi đồng hồ ngay cả khi tất cả chúng đều cần ALU các cổng thực thi.
Xem câu trả lời của tôi về một câu hỏi khác về thanh ghi zeroing để biết thêm chi tiết.
Bài đăng trên blog của Bruce Dawson mà Michael Petch đã liên kết (trong một bình luận về câu hỏi) chỉ ra rằng nó xor
được xử lý ở giai đoạn đăng ký đổi tên mà không cần đơn vị thực thi (không có lỗi trong miền không sử dụng), nhưng bỏ lỡ thực tế là nó vẫn còn một điểm trong miền hợp nhất. Các CPU Intel hiện đại có thể phát hành và gỡ bỏ 4 uops miền hợp nhất trên mỗi xung nhịp. Đó là nơi bắt nguồn của giới hạn 4 số không trên mỗi đồng hồ. Độ phức tạp tăng lên của phần cứng đổi tên thanh ghi chỉ là một trong những lý do giới hạn độ rộng của thiết kế xuống còn 4. (Bruce đã viết một số bài đăng trên blog rất xuất sắc, như loạt bài của anh ấy về toán FP và các vấn đề x87 / SSE / làm tròn , mà tôi làm rất khuyến khích).
Trên các CPU dòng AMD Bulldozer , mov immediate
chạy trên các cổng thực thi số nguyên EX0 / EX1 giống như xor
. mov reg,reg
cũng có thể chạy trên AGU0 / 1, nhưng đó chỉ để sao chép thanh ghi, không phải để thiết lập ngay lập tức. Vì vậy, AFAIK, trên AMD lợi thế duy nhất để xor
qua mov
là mã hóa ngắn hơn. Nó cũng có thể tiết kiệm tài nguyên đăng ký vật lý, nhưng tôi chưa thấy bất kỳ thử nghiệm nào.
Các thành ngữ zeroing được công nhận tránh các hình phạt đăng ký một phần trên CPU Intel. Các thành ngữ này đổi tên các thanh ghi từng phần riêng biệt với các thanh ghi đầy đủ (họ P6 & SnB).
xor
sẽ gắn thẻ thanh ghi là có các phần trên bằng không , do đó xor eax, eax
/ inc al
/ inc eax
tránh hình phạt thanh ghi một phần thông thường mà các CPU trước IvB có. Ngay cả khi không có xor
, IvB chỉ cần một uop hợp nhất khi 8bits ( AH
) cao được sửa đổi và sau đó toàn bộ thanh ghi được đọc, và Haswell thậm chí còn loại bỏ điều đó.
Từ hướng dẫn về microarch của Agner Fog, trang 98 (phần Pentium M, được tham chiếu bởi các phần sau bao gồm cả SnB):
Bộ xử lý nhận ra XOR của một thanh ghi với chính nó khi đặt nó thành 0. Một thẻ đặc biệt trong thanh ghi nhớ rằng phần cao của thanh ghi bằng 0 để EAX = AL. Thẻ này được ghi nhớ ngay cả trong một vòng lặp:
; 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
(từ pg82): Bộ xử lý ghi nhớ rằng 24 bit trên của EAX bằng 0 miễn là bạn không gặp phải sự cố gián đoạn, báo cáo sai hoặc sự kiện tuần tự hóa khác.
pg82 của hướng dẫn đó cũng xác nhận rằng mov reg, 0
là không được công nhận là một thành ngữ zeroing, ít nhất là trên P6 đầu thiết kế như PIII hoặc PM. Tôi sẽ rất ngạc nhiên nếu họ sử dụng các bóng bán dẫn để phát hiện nó trên các CPU sau này.
xor
đặt cờ , có nghĩa là bạn phải cẩn thận khi kiểm tra các điều kiện. Vì setcc
tiếc là chỉ khả dụng với đích 8bit , bạn thường cần phải cẩn thận để tránh bị phạt đăng ký một phần.
Sẽ thật tuyệt nếu x86-64 đặt lại một trong những mã opc đã loại bỏ (như AAM) cho một bit 16/32/64 setcc r/m
, với vị từ được mã hóa trong trường 3 bit của thanh ghi nguồn của trường r / m (cách một số lệnh toán hạng đơn khác sử dụng chúng như các bit opcode). Nhưng họ đã không làm điều đó và điều đó sẽ không giúp ích gì cho x86-32.
Tốt nhất, bạn nên sử dụng xor
/ đặt cờ / setcc
/ đọc thanh ghi đầy đủ:
...
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
Điều này có hiệu suất tối ưu trên tất cả các CPU (không có lỗi, hợp nhất uops hoặc phụ thuộc sai).
Mọi thứ phức tạp hơn khi bạn không muốn thử trước một lệnh thiết lập cờ . Ví dụ: bạn muốn phân nhánh theo một điều kiện và sau đó setcc theo điều kiện khác từ các cờ giống nhau. ví dụ cmp/jle
, sete
và bạn không có một thanh ghi dự phòng, hoặc bạn muốn giữ nguyên xor
đường dẫn mã không được sử dụng.
Không có thành ngữ zeroing nào được công nhận mà không ảnh hưởng đến cờ, vì vậy lựa chọn tốt nhất phụ thuộc vào vi kiến trúc đích. Trên Core2, việc chèn một uop hợp nhất có thể gây ra hiện tượng đình trệ 2 hoặc 3 chu kỳ. Nó có vẻ rẻ hơn trên SnB, nhưng tôi đã không mất nhiều thời gian để đo lường. Việc sử dụng mov reg, 0
/ setcc
sẽ có một hình phạt đáng kể đối với các CPU Intel cũ hơn và vẫn còn tệ hơn trên Intel mới hơn.
Sử dụng setcc
/ movzx r32, r8
có lẽ là giải pháp thay thế tốt nhất cho dòng Intel P6 & SnB, nếu bạn không thể xor-0 trước hướng dẫn cài đặt cờ. Điều đó sẽ tốt hơn là lặp lại thử nghiệm sau một xor-zeroing. (Thậm chí không xem xét sahf
/ lahf
hoặc pushf
/ popf
). IvB có thể loại bỏ movzx r32, r8
(tức là xử lý nó bằng cách đổi tên thanh ghi mà không có đơn vị thực thi hoặc độ trễ, như xor-zeroing). Haswell trở về sau chỉ loại bỏ các mov
lệnh thông thường , do đó movzx
cần một đơn vị thực thi và có độ trễ khác 0, làm cho kiểm tra / setcc
/ movzx
tệ hơn xor
/ kiểm tra / setcc
, nhưng ít nhất vẫn tốt như kiểm tra / mov r,0
/ setcc
(và tốt hơn nhiều trên các CPU cũ hơn).
Việc sử dụng setcc
/ movzx
không có zeroing trước là không tốt trên AMD / P4 / Silvermont, bởi vì chúng không theo dõi các deps riêng biệt cho các thanh ghi phụ. Sẽ có một giá trị sai trên giá trị cũ của sổ đăng ký. Sử dụng mov reg, 0
/ setcc
cho zeroing / dependency-break có lẽ là lựa chọn thay thế tốt nhất khi xor
/ test / setcc
không phải là một lựa chọn.
Tất nhiên, nếu bạn không cần setcc
đầu ra của rộng hơn 8 bit, bạn không cần phải làm gì cả. Tuy nhiên, hãy cẩn thận với các phụ thuộc sai trên các CPU không phải P6 / SnB nếu bạn chọn một thanh ghi gần đây là một phần của một chuỗi phụ thuộc dài. (Và hãy cẩn thận gây ra một phần đăng ký ngừng trệ hoặc tăng thêm nếu bạn gọi một hàm có thể lưu / khôi phục thanh ghi mà bạn đang sử dụng.)
and
với số 0 ngay lập tức không được đặt tên đặc biệt độc lập với giá trị cũ trên bất kỳ CPU nào mà tôi biết, vì vậy nó không phá vỡ các chuỗi phụ thuộc. Nó không có ưu điểm xor
và nhiều nhược điểm.
Nó chỉ hữu ích để viết microbenchmarks khi bạn muốn phụ thuộc như một phần của bài kiểm tra độ trễ, nhưng muốn tạo một giá trị đã biết bằng cách thêm 0 và thêm.
Xem http://agner.org/optimize/ để biết thông tin chi tiết về microarch , bao gồm cả những thành ngữ zeroing nào được coi là phá vỡ sự phụ thuộc (ví dụ: sub same,same
trên một số nhưng không phải tất cả CPU, trong khi xor same,same
được công nhận trên tất cả.) Có mov
phá vỡ chuỗi phụ thuộc trên giá trị cũ của thanh ghi (bất kể giá trị nguồn, bằng không hay không, vì đó là cách mov
hoạt động). xor
chỉ phá vỡ chuỗi phụ thuộc trong trường hợp đặc biệt mà src và dest là cùng một thanh ghi, đó là lý do tại sao mov
bị loại khỏi danh sách các trình ngắt phụ thuộc được công nhận đặc biệt . (Ngoài ra, vì nó không được công nhận là một thành ngữ zeroing, với những lợi ích khác mang lại.)
Điều thú vị là thiết kế P6 lâu đời nhất (từ PPro đến Pentium III) không nhận ra xor
-zeroing như một bộ ngắt phụ thuộc, chỉ là một thành ngữ zeroing nhằm mục đích tránh các gian hàng đăng ký một phần , vì vậy trong một số trường hợp, nó đáng sử dụng cả hai mov
và sau đó xor
-zeroing theo thứ tự đó để phá vỡ dep và sau đó bằng không một lần nữa + thiết lập bit thẻ bên trong mà các bit cao bằng 0 nên EAX = AX = AL.
Xem Ví dụ 6.17 của Agner Fog. trong pdf microarch của anh ấy. Anh ấy nói rằng điều này cũng áp dụng cho P2, P3 và thậm chí (sớm?) PM. Một nhận xét về bài đăng trên blog được liên kết nói rằng chỉ có PPro mới có sự giám sát này, nhưng tôi đã thử nghiệm trên Katmai PIII và @Fanael đã thử nghiệm trên Pentium M và cả hai chúng tôi đều nhận thấy rằng nó không phá vỡ sự phụ thuộc về độ trễ -liên kết imul
chuỗi. Điều này xác nhận kết quả của Agner Fog, thật không may.
Nếu nó thực sự làm cho mã của bạn đẹp hơn hoặc lưu các hướng dẫn, thì chắc chắn, bằng không mov
để tránh chạm vào cờ, miễn là bạn không gây ra vấn đề hiệu suất ngoài kích thước mã. Tránh các cờ làm tắc nghẽn là lý do hợp lý duy nhất để không sử dụng xor
, nhưng đôi khi bạn có thể xor-0 trước thứ đặt cờ nếu bạn có một thanh ghi dự phòng.
mov
-zero trước setcc
có độ trễ tốt hơn so với movzx reg32, reg8
sau (ngoại trừ trên Intel khi bạn có thể chọn các thanh ghi khác nhau), nhưng kích thước mã kém hơn.
mov reg, src
cũng phá vỡ chuỗi dep cho các CPU OO (bất kể src là imm32 [mem]
, hay một thanh ghi khác). Việc phá vỡ sự phụ thuộc này không được đề cập trong sổ tay hướng dẫn tối ưu hóa vì nó không phải là trường hợp đặc biệt chỉ xảy ra khi src và dest là cùng một thanh ghi. Nó luôn xảy ra cho các hướng dẫn không phụ thuộc vào đích của chúng. (trừ trường hợp thực hiện của Intel popcnt/lzcnt/tzcnt
có một dep sai trên dest.)
mov
miễn phí, chỉ có độ trễ bằng không. Phần "không sử dụng cổng thực thi" thường không quan trọng. Thông lượng miền kết hợp có thể dễ dàng là điểm nghẽn, đặc biệt. với tải hoặc cửa hàng trong hỗn hợp.
xor r64, r64
không chỉ lãng phí một byte. Như bạn nói xor r32, r32
là sự lựa chọn tốt nhất đặc biệt là với KNL. Xem phần 15.7 "Các trường hợp độc lập đặc biệt" trong hướng dẫn sử dụng micrarch này nếu bạn muốn đọc thêm.