这article说:
请注意,双变量将在32字节边界上分配 位机,需要两个存储器读周期。
所以这意味着x86 CPU有一些读取double
值的指令(我认为x86 CPU可以读取的最大值是4个字节!)。任何人都可以提供读取双精度值的指令示例吗?
答案 0 :(得分:3)
任何人都可以提供读取双精度值的指令示例吗?
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字节,因此内存(逻辑上)以CPU内部和核心之间的64字节块进行复制。 Intel CPU在内核之间使用32字节总线(在通往内存的路上使用L2和L3之间)。
有关手册和(准确)文档/指南/文章的许多良好链接,请参阅x86 tag wiki。如果TutorialsPoint或GeeksForGeeks等网站上的某些内容看起来令人困惑,或者与您在其他地方阅读的内容不匹配,那么它很有可能是错误的。如果没有SO这样的投票机制,那么不准确的内容就不会被淘汰。
让我们直接记录http://www.geeksforgeeks.org/structure-member-alignment-padding-and-data-packing/。它充满了错误的信息,它所说的关于硬件的大部分内容可能在1995年大致正确,但现在还没有。很多逻辑/推理都是错误的。
它甚至不包含单词" cache"!谈论访问RAM和"银行"对现代x86 CPU来说完全是胡说八道。同样的概念适用于分区缓存,因此对于某些CPU来说,16B或32B等对齐边界很重要,即使所有x86 CPU上的缓存行都是64B(自Pentium III以来)。
现代x86通常具有非常好的非对齐访问支持,特别是对于8B和更窄,但在AMD CPU(包括Ryzen)上跨越16B或especially 32B boundary可能会受到惩罚。
在最新的处理器上,我们得到
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)
system call,您传递指针并且内核在结构中写入字段。如果您的代码与内核不同意内存中哪些字节代表哪些C struct
成员,那么您的代码将无法正常工作。指定布局/对齐规则(以及调用约定)是ABI的主要部分。
很可能是"更新的"测试针对的是32位i386 System V psABI,而#34;更老的" test正在为x86-64 System V psABI(或Windows 32或64位,它们都有24字节structc
和MSVC CL19)编译64位代码。
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);
Compiler output for clang -m32
(Godbolt compiler explorer):
alignof_double:
.long 8
c_offset_d:
.long 4
所以 32位ABI会在结构中错位double
,即使它更喜欢将double
与其他地方的8个字节对齐,但是64位ABI赢了& #39;吨。 i386 System V ABI可以追溯到很久以前,可能是实际的386或486个CPU,可能确实需要两个内存读取周期来加载double
。仅考虑高达4B的对齐边界的打包规则对旧CPU或32位模式下的整数有意义。新设计的32位ABI可能需要double
对齐,也可能int64_t
(用于MMX / SSE2)。但是,打破ABI兼容性以在struct
内部对齐64位类型将是不值得的。
有关ABI文档,请参阅x86标记wiki。
请注意std::atomic<double>
即使在-m32
中也会获得完整的8B对齐。
请注意,双变量将在32位机器上的8字节边界上分配,并且需要两个内存读取周期。
对64位对齐地址的qword加载或存储(例如fld
或fstp
)保证是原子的(自P5奔腾以来),因此它绝对是单一访问到L1D缓存(或用于未缓存访问的RAM)。请参阅Why is integer assignment on a naturally aligned variable atomic?。
此保证一般适用于x86(包括AMD和其他供应商)。
实际上,gcc -m32
使用SSE2 std::atomic<int64_t>
或x87 movq
加载/存储来实现fild
。
更宽和/或未对齐的加载/存储不是保证是单一访问,但是在某些CPU上。例如对于未跨越64B缓存线边界的未对齐数据,Intel Haswell / Skylake可以在每个周期执行两次32B未对齐矢量加载,每次加载作为L1D缓存的单次读取。如果它确实越过了缓存行边界(如vmovups ymm0, [rdi+33]
,其中rdi
是64B对齐的),则吞吐量限制为每个周期一个,因为每个加载必须从两个缓存行读取和合并数据。
对未对齐负载的硬件支持非常好,因此只需要额外的负载使用延迟。然而,4k-splits更昂贵,特别是在Skylake之前。
值得注意的是,大多数处理器都有数学协处理器,称为浮点单元(FPU)。代码中的任何浮点运算都将转换为FPU指令。 主处理器与浮点执行无关。
这(以及之后的挥手)完全是虚假的。自Pentium以来,FPU已集成到主CPU核心中。 FP加载和整数加载在大多数CPU中使用相同的执行端口。
一个例外是AMD Bulldozer系列,其中一对整数核心共享一个FP /向量单元。但它们的耦合非常紧密,FP负载仍然使用相同的dTLB和L1D缓存。
根据David Kanter's Bulldozer writeup:,有一个小的浮点加载缓冲区(上面未显示),它作为加载存储单元和FP簇之间负载的模拟管道。
即使Bulldozer仍然在整数和FP /向量uop之间共享一个无序ReOrder缓冲区(ROB),并且整数/ FP指令必须按程序顺序退出(一如既往地支持精确异常)。其他AMD设计也有单独的调度程序,但这是一件小事。
Intel CPU对整数和FP使用单个统一的无序调度程序,执行端口具有整数和FP ALU的混合。例如,Haswell port 0 can run integer shift and simple ALU uops, and it also has a vector multiply / FMA unit.
与PowerPC不同,从FP存储到整数负载的存储转发工作正常。 (在PPC上,Load-Hit-Store档位显然是一个问题。在x86上,它只比普通的存储转发工作没有太多问题。在Bulldozer上它很慢,ALU {{1}也是如此},因为协调2个核心与一个FPU通信。)
按照标准,double类型将占用8个字节。并且,在FPU中执行的每个浮点运算都将是64位长度。在执行之前,偶数
movd r32, xmm
类型将被提升为64位。
也是假的。对于x87,内部寄存器为80位(64位尾数!)。除非将x87精度控制寄存器设置为53位尾数或24位尾数,否则此描述适用于x87。 (参见Bruce Dawson的优秀系列浮点文章。this one about Intermediate Float Precision提到在Windows上,D3D9库将x87 FPU设置为24位精度,因此除以sqrt会更快一些,并且旧版本的MSVCRT将其设置为53位float
!)
但是,由于本文讨论的是64位计算机,忽略x86-64 Windows和Linux在double
寄存器中传递/返回FP args的事实是错误的,而且#&# 39;假设FP数学将使用SSE / SSE2标量或向量指令完成,而不是x87。像xmm
这样的SSE2指令在xmm寄存器中生成IEEE二进制64结果,因此它们在每一步之后舍入到53位尾数精度。 (如果你想要更快的分割,你可以使用mulsd
而不是divps
。SSE没有精确控制寄存器;你只需要使用不同的指令。)
将divpd
传递给float
等可变参数函数会根据C的默认促销规则将其提升为printf
,但double
不会将其float a = f1 * f2;
推广到float
。必须提升加倍,然后将结果舍入为double
。
64位长度的FPU寄存器强制在8字节边界上分配double类型。 [...]
因此,对于双类型(预期在8字节边界上),地址解码将是不同的。这意味着,浮点单元的地址解码电路不会有最后3个引脚。
总废话。 x87(fld
)和SSE2(movsd
)支持未对齐的qword fld
次加载/存储,自{80}起0xabc001
。
少数处理器不会有最后两个地址线,这意味着无法访问奇数字节边界。
以这种方式设计的CPU可以通过总线执行32位加载并提取所需的字节。这种说法是为什么它如此愚蠢,文章没有提到缓存。
有趣的事实是:旧版本的ARM使用地址的低2位作为字节旋转。因此,从0xabc000
加载可以获得{{1}}处应用了旋转的4个字节。我听说调试很有趣,而硬件只是在未对齐的负载上出现问题:P
早期的Alpha CPU确实没有字节加载支持,所以你总是需要进行32或64位加载和掩码和/或移位以获得你想要的字节。
我可以更多地谈论本文所暗示的错误......
我确定如果我仔细阅读,我会看到更多问题,但这是我从略读中得到的问题。这篇文章的作者在20年前阅读了一些关于硬件的内容,并在此基础上提出了一些错误的想法。