Matt Godbolt,可能最廣為人知的是他是 Compiler Explorer 的經營者,他寫了一篇簡短的文章,探討了 x86 編譯器為何偏愛 `xor eax, eax` 這條指令。
答案是,這是 x86 上將暫存器設為零的最緊湊方式。特別是,它比更明顯的 `mov eax, 0` 指令要短好幾個位元組,因為它避免了編碼四位元組的常數。x86 架構沒有專用的零暫存器,所以如果你需要將暫存器歸零,你必須從頭開始。
但是 Matt 並沒有解釋為何大家選擇 xor 而不是其他保證會產生零的數學運算?特別是,`sub eax, eax` 有什麼問題?它的編碼位元組數相同,執行週期數也相同。而且它在旗標(flags)方面的行為甚至更好:
請注意,`xor eax, eax` 會讓 AF 旗標處於未定義狀態,而 `sub eax, eax` 則會清除它。
我不知道 xor 為何贏得了這場戰鬥,但我懷疑這只是「蜂擁」效應的案例。
在我虛構的歷史中,xor 和 sub 最初受歡迎程度差不多,但 xor 由於某種偶然的因素,可能因為感覺更「聰明」,而取得了一點點領先。
當早期編譯器使用 xor 來歸零暫存器時,這就開始了滾雪球效應,因為人們會看到編譯器產生 xor,並認為:「嗯,這些編譯器寫得真聰明,他們一定知道一些我不知道的事情。既然我在 xor 和 sub 之間猶豫不決,這微小的一點數據就足以讓我傾向於 xor。」
這些將暫存器歸零的慣用法的普及,促使 Intel 在指令解碼前端加入了對 `xor r, r` 和 `sub r, r` 的特殊偵測,並將目標重新命名為內部零暫存器,完全繞過了指令的執行。你可以想像,在某種意義上,該指令「執行零週期」。前端偵測也打破了依賴鏈:正常情況下,xor 或 sub 的輸出依賴於其輸入,但在這種將暫存器與自身進行 xor 的特殊情況下,我們知道輸出是零,與輸入無關。
儘管 Intel 加入了對 xor 和 sub 的偵測支援,但 Stack Overflow 擔心其他 CPU 製造商可能只為 xor 進行了特殊處理,而沒有為 sub 進行,這使得 xor 在這場最終毫無意義的戰鬥中勝出。
一旦一條指令獲得了優勢,即使只是極其微小的優勢,也足以改變天平,並讓所有人都傾向於那一方。
額外閒聊:我的一位前同事偏愛使用 `sub r, r` 來歸零暫存器,當我閱讀組合語言程式碼時,我能認出他是作者,因為他使用 sub 來歸零暫存器,而不是更流行的 xor。
額外額外閒聊:xor 的技巧不適用於 Itanium,因為數學運算不會重置 NaT 位元。幸運的是,Itanium 也有專用的零暫存器,所以你不需要這個技巧。你只需要將零移到你想要的目標即可。
Raymond 參與了 Windows 的演進超過 30 年。2003 年,他創立了一個名為 The Old New Thing 的網站,該網站的受歡迎程度遠超他最瘋狂的想像,這發展至今仍讓他感到不安。該網站催生了一本書,巧合地也名為 The Old New Thing (Addison Wesley 2007)。他偶爾會出現在 Windows Dev Docs 的 Twitter 帳號上講述一些沒有實際資訊的故事。
討論已關閉。登入以編輯/刪除現有評論。
使用 XOR 應該會導致更低的功耗。為了執行 SUB,邏輯需要對數字進行補碼,然後運行加法器,為了達到高性能,加法器不能使用簡單的進位前導。XOR 實際上只需要每個位元一個 XOR 閘,因此每次操作的總能量要低幾個數量級。
但請記住,這在很久以前可能很重要。以現代技術而言,我們可能只節省了不到一個皮焦耳的能量。僅指令提取和解碼所需的能量,尤其是在 CISC 處理器中,要高得多,因此實際的能量差異微乎其微。所以除了看起來很酷之外,它可能還曾在過去帶來了電力效益。
被識別為暫存器清除操作。它們通常由暫存器重命名處理,因此根本不涉及對暫存器內容執行操作。
感謝澄清。但歷史解釋仍然適用!
AF(輔助進位旗標)僅用於 BCD 指令 DAA、DAS、AAA 和 AAS。
很少(如果有的話)編譯器會發出這些指令,即使是手寫的組合語言程式碼也很少見。在長模式(64 位元程式碼)中,它們不再可用。
總而言之,AF 處於定義狀態非常罕見(幾乎沒有)。
AF 是 PUSHF 儲存的狀態的一部分。如果因為某些原因你試圖驗證某個程式在不同機器上是否運行得「完全相同」,記憶體可能會因為 AF 結果不同而產生差異。(這種驗證似乎更可能針對核心程式,而不是大多數使用者空間程式。)仍然非常罕見,但與 BCD 指令的使用無關。
是的,即使你關心那些用 XOR 設定 AF 的罕見 CPU,這也幾乎不成問題。
有一些保留位元的數值也不是保證的,所以如果你執行 pushf,已經有一些位元是你應該忽略的。至少在 64 位元程式碼中,你幾乎肯定希望忽略 AF。
另一個原因是非 x86 ISA(如 Itanium,還有 AArch64)無法為 XOR 進行打破依賴的歸零慣用語,是記憶體依賴排序(memory_order_consume)。只有 x86 將每個載入視為獲取。其他需要架構規則來傳遞依賴性,以確保你可以執行像 ptr[tmp-tmp](在組合語言中 load / sub / load)這樣的操作,並且仍然讓該載入操作在先前的 tmp=data_ready.load(consume) 之後排序。(通常在 data-ready 旗標之間有一個分支,但也用於指向的資料。)
此外,在 RISC 或 VLIW 架構上花費電晶體來檢查歸零慣用語沒有太大意義,因為與 move-immediate 相比,它不會節省程式碼大小,而 move-immediate 對於任何值都是自動打破依賴的。後端執行單元的效益很小。
順帶一提,Intel Silvermont 只識別 XOR 作為慣用語,而不識別 SUB。(待查目前的 E 核,如 Gracemont / Crestmont。)我在 https://stackoverflow.com/questions/33666617/what-is-the-best-way-to-set-a-register-to-zero-in-x86-assembly-xor-mov-or-and/33668295#33668295 中提到了這一點,我更新了我連結的「有多少種方法」的回答,不僅僅是說「可能」。
另一位評論者指出,Via Nano 2000 也是如此,只有 Nano 3000 才增加了對 SUB 作為歸零慣用語的識別。
Z80 上的 xor 不是更好嗎?人們只是出於習慣繼續在 8080 等架構上使用它?我試圖確認,但世界比我年輕得多,所以我找不到快速的答案。
我查閱了資料手冊——在 8008、8080、8085 和 Z80 上,SUB 和 XOR 在指令位元組碼和時脈週期方面是等效的;此外,看起來所有 ALU 操作(包括 SUB 和 XOR)都會影響旗標,所以你甚至從 XOR 中得不到這種好處。4004 和 4040 沒有 XOR,MC6800 和 6502 也沒有 `xor r,r` 指令。
根據我到目前為止的發現,我猜測你必須離開微處理器的世界,進入小型電腦,才能找到一個 XOR 比 SUB 更好的 CPU——我找不到任何單晶片 ALU 的 XOR 比 SUB 有更好的時序,所以我預計你必須尋找由多個晶片組成的(或早於 IC 的)機器,才能找到 XOR 勝過 SUB 的情況。