请考虑以下代码:
void add(double& a, double b) {
a += b;
}
根据godbolt在Skylake上编译为:
add(double&, double):
vaddsd xmm0, xmm0, QWORD PTR [rdi]
vmovsd QWORD PTR [rdi], xmm0
ret
如果我从不同的线程(对于相同的变量add(a, 1.23)
)调用add(a, 2.34)
和a
,则肯定会以a + 1.23,a + 2.34或a + 1.23 + 2.34结束吗?
也就是说,在此程序集中,这些结果之一肯定会发生,并且a
不会以其他某种状态结束吗?
这是与我有关的问题:
CPU是否通过一次操作获取您正在处理的单词?
某些处理器可能通过一次接两次的获取(当然不是原子方式)来允许访问一个恰好在内存中未对齐的变量的内存。
在那种情况下,如果在第一个线程已经获取了该单词的第一部分的同时另一个线程插入了对该内存区域的写操作,就会出现问题,而在另一个线程已经对该单词进行了修改的情况下又获取了第二部分。
thread 1 fetches first part of a XXXX thread 1 fetches second part of a YYYY thread 2 fetches first part of a XXXX thread 1 increments double represented as XXXXYYYY that becomes ZZZZWWWW by adding b thread 1 writes back in memory ZZZZ thread 1 writes back in memory WWWW thread 2 fetches second part of a that is now WWWW thread 2 increments double represented as XXXXWWWW that becomes VVVVPPPP by adding b thread 2 writes back in memory VVVV thread 2 writes back in memory PPPP
为了保持紧凑,我使用一个字符表示8位。
现在XXXXWWWW
和VVVVPPPP
将表示总的浮点值与您期望的值不同。那是因为您最终混合了双变量的两个不同二进制表示形式(IEEE-754)的两个部分。
表示,我知道在某些基于ARM的体系结构中不允许数据访问(这将导致生成陷阱),但我怀疑Intel处理器确实允许这样做。
因此,如果您的变量a
对齐,则结果可以是以下任意一种>>
,对于其他正在阅读此答案的人:在这些情况下使用a + 1.23,a + 2.34,a + 1.23 + 2.34
如果您的变量可能未对齐(即地址不是8的倍数,则您的结果可以是以下任何一种)>
a + 1.23,a + 2.34,a + 1.23 + 2.34或垃圾值
] >>[作为进一步说明,请记住,即使您的环境
alignof(double) == 8
不一定足以得出结论,也不会出现对齐问题。全部取决于您的特定变量来自何处。考虑following:#pragma push() #pragma pack(1) struct Packet { unsigned char val1; unsigned char val2; double val3; unsigned char val4; unsigned char val5; }; #pragma pop() int main() { static_assert(alignof(double) == 8); double d; add(d,1.23); // your a parameter is aligned Packet p; add(p.val3,1.23); // your a parameter is now NOT aligned return 0; }
因此断言
alignof()
不一定保证您的变量是对齐的。如果您的变量不包含在任何包装中,那么您应该可以。请给我一个免责声明
std::atomic<double>
是实现线程安全的实现工作量和性能的最佳折衷方案。有一些具有特殊高效指令的CPU体系结构,这些指令可用于处理原子变量而无需注入大量障碍。最终可能已经满足了您的性能要求。