簡體   English   中英

在某些情況下,在x86-64 Intel / AMD CPU上,128bit / 64bit硬件無符號除法能否比64bit / 32bit除法更快?

[英]Can 128bit/64bit hardware unsigned division be faster in some cases than 64bit/32bit division on x86-64 Intel/AMD CPUs?

可以通過硬件128bit / 64bit除法指令執行縮放的64bit / 32bit除法,例如:

; Entry arguments: Dividend in EAX, Divisor in EBX
shl rax, 32  ;Scale up the Dividend by 2^32
xor rdx,rdx
and rbx, 0xFFFFFFFF  ;Clear any garbage that might have been in the upper half of RBX
div rbx  ; RAX = RDX:RAX / RBX

...在某些特殊情況下,比硬件64位/ 32位除法指令執行的縮放64位/ 32位除法更快,例如:

; Entry arguments: Dividend in EAX, Divisor in EBX
mov edx,eax  ;Scale up the Dividend by 2^32
xor eax,eax
div ebx  ; EAX = EDX:EAX / EBX

“某些特殊情況”是指異常的紅利和除數。 我只對比較div指令感興趣。

您正在問關於將uint64_t / uint64_t C除法優化為64b / 32b => 32b x86 asm除法(已知除數為32位)的問題。 當然,編譯器必須避免在完全有效的(在C語言中)64位除法中出現#DE異常的可能性,否則,它就不會遵循as-if規則。 因此,只有在商數可以容納32位的情況下,它才能執行此操作。

是的,那是一場勝利,或者至少是收支平衡。 在某些CPU上,甚至值得在運行時檢查這種可能性,因為64位除法速度要慢得多。 但不幸的是當前的x86編譯器不具有優化通尋找這個優化 ,即使你設法給他們足夠的信息,他們可以證明它是安全的。 例如, if (edx >= ebx) __builtin_unreachable(); 上次嘗試沒有幫助。


對於相同的輸入,32位操作數大小將始終至少與之一樣快

16或8位可能比32慢,因為它們可能會有錯誤的依賴性來寫入輸出,但是為了避免這種情況,寫入32位寄存器零擴展到64。 (這就是mov ecx, ebx是將ebx零擴展到64位的好方法的原因,比harhar所指出的要好, and該值不能編碼為32位符號擴展的立即數。) 但是,除了部分寄存器的惡作劇外,16位和8位除法運算速度通常也與32位一樣快,甚至還不差。

在AMD CPU上,除法性能不取決於操作數大小,而僅取決於數據 128/64位的0 / 1應該比任何較小的操作數大小的最壞情況都要快。 AMD的整數除法指令只有2微秒(大概是因為它必須寫入2個寄存器),所有邏輯都在執行單元中完成。

Ryzen上的16位/ 8位=> 8位除法是單個uop(因為它只需要寫AH:AL = AX)。


在Intel CPU上, div / idiv被微編碼為盡可能多的微碼 對於最大32位(Skylake = 10)的所有操作數大小,大約相同的uops數量,但是64位慢得多 (Skylake div r64為36 div r64 ,Skylake idiv r64為57 idiv r64 )。 請參閱Agner Fog的說明表: https ://agner.org/optimize/

在Skylake上,最大32位操作數大小的div / idiv吞吐量固定為每6個周期1個。 但是div/idiv r64吞吐量是每24-90個周期之一。

對於特定的性能實驗,通過修改現有二進制文件中的REX.W前綴將div r64更改為div r32 在Windows上,Trial-division代碼在32位上的運行速度比Linux64位上運行的快2倍。吞吐量差異。

為什么Clang僅從Sandy Bridge開始才做這種優化技巧? 顯示了當英特爾CPU進行調整時,當股息較小時,機會性地使用32位除法的clang。 但是您有一個大紅利和一個足夠大的除數,這是一個更復雜的情況。 那種clang優化仍然使asm的上半部分清零,從不使用非零或非符號擴展的EDX。


當將一個無符號的32位整數(左移32位)除以另一個32位整數時,我未能使流行的C編譯器生成后者的代碼。

我假設你投的是32位整數uint64_t 第一 ,避免UB,並得到一個正常的uint64_t / uint64_t在C抽象機。

這是有道理的: 您的方式將不安全,當edx >= ebx時,它將以#DE錯誤。 當商溢出AL / AX / EAX / RAX而不是默默截斷時,x86除法會發生故障。 無法禁用它。

所以編譯器通常只使用idivcdqcqo ,和div只有零上半部后,除非您使用的是內在的或內聯匯編來打開自己到你的代碼出錯的可能性。 在C語言中, x / y僅在y = 0發生故障(或者對於有符號, INT_MIN / -1也允許發生故障1 )。

GNU C沒有用於寬除的內在函數, 但是MSVC具有_udiv64 (對於gcc / clang,大於1的寄存器除法使用輔助函數,該函數會嘗試針對少量輸入進行優化。但是,這對於64位計算機上的64/32除法沒有幫助,其中GCC和clang僅使用128 / 64位除法指令。)

即使有某種方法可以向編譯器保證您的除數足夠大以使商適合32位,但根據我的經驗,當前的gcc和clang並不會尋求這種優化。 對於您的情況而言,這將是一個有用的優化(如果總是安全的話),但是編譯器不會尋找它。


腳注1:更具體地說,ISO C將這些情況描述為“未定義的行為”。 一些ISA(如ARM)具有無故障的划分指令。 C UB表示可能發生任何事情,包括僅截斷為0或其他整數結果。 請參見為什么將-1除以整數(負數)會導致FPE? 有關AArch64與x86代碼生成和結果的示例。 允許故障並不意味着需要故障。

在某些情況下,在x86-64 Intel / AMD CPU上,128bit / 64bit硬件無符號除法能否比64bit / 32bit除法更快?

從理論上講,一切皆有可能(例如,在50年后,Nvidia會創建一個80x86 CPU ...)。

但是,我想不出一個單一的合理原因,為什么在x86-64上128bit / 64bit的分割速度會比(不僅等同於)64bit / 32bit的分割速度更快。

我懷疑這是因為我假設C編譯器作者非常聰明,並且到目前為止,當我將無符號的32位整數(左移32位)除以另一個32位整數時,我未能使流行的C編譯器生成后一個代碼。 。 它始終編譯為128位/ 64位div指令。 PS左移編譯為shl很好。

編譯器開發人員很聰明,但是編譯器很復雜,並且C語言規則妨礙了編譯。 例如,如果您只是執行a a = b/c; b為64位, c為32位)時,該語言的規則是c在除法發生之前被提升為64位,因此最終以某種中間語言成為64位除數,這使得后端翻譯(從中間語言到匯編語言)很難說出64位除數可以是32位除數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM