簡體   English   中英

CPU如何讀取double值?

[英]How does the CPU reads a double value?

這篇文章說:

請注意,雙變量將在 32 位機器上的 8 字節邊界上分配,並且需要兩個內存讀取周期。

所以這意味着 x86 CPU 有一些指令可以讀取double值(我認為 x86 CPU 可以讀取的最大值是 4 個字節!)。 任何人都可以提供讀取雙精度值的指令示例嗎?

任何人都可以提供讀取雙精度值的指令示例嗎?

MOVSD xmm0, [rsi]加載 8 個字節並將 xmm0 的上半部分置零。
對於舊版 x87,有fld qword [rsi] 當然,您可以將內存操作數用於 ALU 指令,例如addsd xmm0, [rsi]
或者對於 AVX,有諸如vbroadcastsd ymm0, [rsi]vaddsd xmm1, xmm0, [rsi]

所有這些都在所有現代 x86 CPU 上解碼一個 uop,並對緩存進行一次訪問。

我認為 x86 CPU 可以讀取的最大值是 4 個字節!

嗯? 從 8086 開始就支持 8 字節 x87 加載。在 64 位模式下, mov rax, [rdi]pop rax都是 8 字節加載。

使用 AVX,您可以執行vmovups ymm0, [rsi + rdx] (即使在 32 位模式下)來執行 32字節加載。 或者使用 AVX512, vmovups zmm0用於 64 字節的加載或存儲。

在現代 CPU 上,緩存線是 64 字節,因此內存(邏輯上)以 64 字節塊的形式在 CPU 內部和內核之間復制。 Intel CPU 在內核之間(以及通往內存的 L2 和 L3 之間)使用 32 字節總線。


請參閱標簽 wiki以獲取許多指向手冊和(准確的)文檔/指南/文章的良好鏈接。 如果像 TutorialsPoint 或 GeeksForGeeks 這樣的網站上的某些內容看起來令人困惑或與您在其他地方閱讀的內容不符,則很有可能是錯誤的。 如果沒有像 SO 那樣的投票機制,不准確的內容就不會被淘汰。

讓我們直接記錄一下http://www.geeksforgeeks.org/structure-member-alignment-padding-and-data-packing/ 它充滿了錯誤的信息,而且它所說的關於硬件的大部分內容在 1995 年可能大致正確,但現在不是。 即使在那時,很多邏輯/推理也是錯誤的。

它甚至不包含“緩存”這個詞! 對於現代 x86 CPU 來說,談論對 RAM 和“銀行”的訪問完全是無稽之談。 同樣的概念適用於存儲緩存,因此 16B 或 32B 之類的對齊邊界對某些 CPU 很重要,即使所有 x86 CPU 上的緩存線都是 64B(在 Pentium III 左右之后)。

現代 x86 總體上具有非常好的非對齊訪問支持,尤其是對於 8B 和更窄的訪問,但在包括 Ryzen 在內的 AMD CPU 上跨越 16B 或特別是 32B 邊界可能會受到懲罰。

在最新的處理器上,我們將struct_c大小設為 16 字節。 [...]

在使用相同工具集(GCC 4.7)的舊處理器(AMD Athlon X2)上,我得到的struct_c大小為 24 字節。 大小取決於內存組在硬件級別的組織方式。

這顯然是無稽之談。 struct布局對於針對相同 ABI 的所有編譯器必須相同,無論使用什么-march=pentium3-mtune=znver1設置,或者您在什么硬件上編譯,以便您可以鏈接傳遞(指向) 將代碼中的struct類型轉換為庫函數,反之亦然。 一個明顯的例子是stat(const char *pathname, struct stat *statbuf)系統調用,你傳遞一個指針,內核在結構中寫入字段。 如果您的代碼在內存中哪些字節代表哪些 C struct成員方面與內核struct ,那么您的代碼將無法工作。 指定布局/對齊規則(和調用約定)是 ABI 的主要部分。

很可能“較新”的測試是針對 32 位 i386 System V psABI,而“較舊”的測試是為 x86-64 System V psABI(或 Windows 32 或 64 位,兩者都編譯 64 位代碼)有 24 字節的structc和 MSVC CL19)。

typedef struct structc_tag {
   char        c;
   double      d;
   int         s;
} structc_t;
int sc = sizeof(structc_t);

#include  <stddef.h>
int alignof_double = alignof(double);
int c_offset_d = offsetof(structc_t, d);

clang -m32 (Godbolt 編譯器資源管理器)的編譯器輸出

alignof_double:
    .long   8
c_offset_d:
    .long   4

因此32 位 ABI 將在 struct 內未對齊double ,即使它更喜歡將double對齊到其他地方的 8 個字節,但 64 位 ABI 不會。 i386 System V ABI 可以追溯到很長一段時間,可能是實際的 386 或 486 CPU,它們可能確實需要兩個內存讀取周期才能加載double . 僅遵守最多 4B 對齊邊界的打包規則對於舊 CPU 或 32 位模式下的整數有意義。 新設計的 32 位 ABI 可能需要對齊double ,也可能需要int64_t (用於 MMX/SSE2)。 但是破壞 ABI 兼容性以在struct對齊 64 位類型是不值得的。

有關 ABI 文檔,請參閱標記 wiki。

請注意,即使在-m32std::atomic<double>也確實獲得了完整的 8B 對齊。

請注意,雙變量將在 32 位機器上的 8 字節邊界上分配,並且需要兩個內存讀取周期。

對 64 位對齊地址的 qword 加載或存儲(例如fldfstp )保證是原子的(自 P5 Pentium 起),因此它絕對是對 L1D 緩存(或 RAM 進行非緩存訪問)的單次訪問。 請參閱為什么在 x86 上對自然對齊的變量原子進行整數賦值? .

此保證一般適用於 x86(包括 AMD 和其他供應商)。 事實上, gcc -m32使用 SSE2 movq或 x87 fild加載/存儲實現std::atomic<int64_t>

保證更寬和/或未對齊的加載/存儲是單次訪問,​​但在某些 CPU 上。 例如,對於不跨越 64B 緩存線邊界的未對齊數據,Intel Haswell/Skylake 可以在每個周期執行兩個 32B 未對齊矢量加載,每個加載作為從 L1D 緩存的單次讀取。 如果它確實跨越了緩存行邊界(如vmovups ymm0, [rdi+33] ,其中rdi是 64B 對齊的),則吞吐量將被限制為每個周期一個,因為每個負載都必須從兩個緩存行讀取和合並數據。

對未對齊負載的硬件支持非常好,因此它只會花費一些額外的負載使用延遲。 不過 4k 分割更貴,尤其是在 Skylake 之前。

需要注意的是,大多數處理器都有數學協處理器,稱為浮點單元 (FPU)。 代碼中的任何浮點運算都會被翻譯成 FPU 指令。 主處理器與浮點執行無關。

這(以及之后的揮手)完​​全是假的 自 486DX 以來,FPU 已集成到主 CPU 內核中。 P6 (Pentium Pro) 甚至添加了直接設置整數 EFLAGS ( fcomi ) 的 x87 指令和讀取整數 EFLAGS 的fcmovcc FP 和整數加載/存儲甚至在現代 Intel CPU 中使用相同的執行端口。

一個例外是 AMD Bulldozer 系列,其中一對整數內核共享一個 FP/矢量單元。 但是它們仍然非常緊密地耦合,並且 FP 加載仍然使用相同的 dTLB 和 L1D 緩存。

根據David Kanter 的 Bulldozer 文章有一個小的浮點加載緩沖區(上面未顯示),它充當加載存儲單元和 FP 集群之間的負載的類似管道。 (即用於存儲轉發。)

即使 Bulldozer 仍然在整數和 FP/向量 uops 之間共享一個無序的重新排序緩沖區 (ROB),並且整數/FP 指令必須按程序順序退出(一如既往地支持精確的異常)。 其他 AMD 設計也有單獨的調度程序,但這只是小事。

Intel CPU 對整數和 FP 使用一個統一的無序調度程序,執行端口混合了整數和 FP ALU。 例如, Haswell 端口 0 可以運行整數移位和簡單的 ALU uop,它還有一個向量乘法/FMA 單元。

與 PowerPC 不同,從 FP 存儲到整數加載的存儲轉發工作正常。 (在 PPC 上,Load-Hit-Store 停頓顯然是一個問題。在 x86 上,它的工作與常規存儲轉發相比沒有更多問題。在 Bulldozer 上它很慢,ALU movd r32, xmm也是如此,因為協調2 個內核與一個 FPU 通信。)

按照標准,double 類型將占用 8 個字節。 而且,在 FPU 中執行的每個浮點運算的長度都是 64 位。 甚至float類型也會在執行前提升為 64 位。

也是假的。 對於 x87,內部寄存器是 80 位(64 位尾數!)。 這個描述有點適合 x87,除非你將 x87 精度控制寄存器設置為 53 位尾數或 24 位尾數。 (參見 Bruce Dawson 的優秀浮點系列文章。 這篇關於中級浮點精度的文章提到,在 Windows 上,D3D9 庫將 x87 FPU 設置為 24 位精度,因此除法和 sqrt 會更快一些,而舊版本的MSVCRT 將其設置為 53 位double精度!)

但是由於本文討論的是 64 位機器,因此忽略 x86-64 Windows 和 Linux 在xmm寄存器中傳遞/返回 FP args 的事實是一個大錯誤,並且假設 FP 數學將使用 SSE/SSE2 完成標量或向量指令,而不是 x87。 mulsd這樣的 SSE2 指令在 xmm 寄存器中生成一個 IEEE binary64 結果,因此它們在每一步后四舍五入到 53 位尾數精度。 (如果你想要更快的除法,你可以使用divps而不是divpd沒有精確控制寄存器;你只需使用不同的指令。)

float傳遞給像printf這樣的可變參數函數會根據 C 的默認提升規則將其提升為double ,但float a = f1 * f2; 不必提升為 double ,然后將結果向下舍入為float

FPU 寄存器的 64 位長度強制在 8 字節邊界上分配雙精度型。 [...]

因此,地址解碼對於 double 類型(預計在 8 字節邊界上)會有所不同。 這意味着,浮點單元的地址譯碼電路不會有最后 3 個引腳。

完全是胡說八道。 x87 ( fld ) 和 SSE2 ( movsd ) 支持未對齊的 qword double加載/存儲,並且自 8086 以來一直支持fld

很少有處理器沒有最后兩條地址線,這意味着無法訪問奇數字節邊界。

以這種方式設計的 CPU 只能通過總線執行 32 位加載並提取所需的字節。 這種論點就是為什么這篇文章沒有提到緩存如此愚蠢。

不過,有趣的事實是:舊版本的 ARM 使用地址的低 2 位作為字節循環。 因此,從裝載0xabc001會得到你的4個字節0xabc000用旋轉應用。 我聽說與在未對齊的負載上出現故障的硬件相比,調試起來很有趣:P

早期的 Alpha CPU 確實沒有字節加載支持,因此您總是必須執行 32 位或 64 位加載和掩碼和/或移位才能獲得所需的字節。

我可以說更多關於這篇文章暗示的錯誤的事情......


我敢肯定,如果我仔細閱讀,我會發現更多問題,但這就是我從略讀中得到的。 這篇文章的作者在20年前讀了一些關於硬件的東西,並在此基礎上捏造了一些錯誤的想法。

暫無
暫無

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

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