原子操作在不同大小上的性能比较
Performance comparison of atomic operations on different sizes
在处理器的自然字大小(4 字节或 8 字节)上运行的原子操作的性能与在其他大小(2 字节或 1 字节)上运行的性能相比如何?
如果我需要维护一个布尔原子变量,我试图弄清楚最佳实践是什么:使用 1 字节优化 space,或使用 4/8 字节优化 (可能)优化性能。
http://agner.org/optimize/ 了解更多详情。
在 x86 上,一个 1 字节数据的数组应该不错。它可以用 movzx
(零扩展)加载,就像用普通的 mov
.
一样快
x86 具有支持原子位域的位操作,如果您想将数据打包为另一个 8 倍。不过,我不确定编译器在为这种情况编写高效代码方面做得如何。即使是只写操作也需要一个缓慢的原子 RMW 周期来处理包含您要写入的位的字节。 (在 x86 上,它会是一个 lock OR
指令,这是一个完整的内存屏障。在 Intel Haswell 上它是 8 微指令,而字节存储是 1 微指令。吞吐量的 19 倍。)这可能仍然值得如果这意味着很多缓存未命中和很少缓存未命中之间的区别,尤其是。如果大部分访问是只读的。 (读位很快,和非原子情况完全一样。)
2 字节(16 位)操作在 x86 上可能很慢,尤其是。在英特尔 CPU 上。当 Intel 指令解码器必须解码带有 16 位立即操作数的指令时,它们的速度会大大降低。这是操作数大小前缀中的 dreaded LCP stall。 (8b ops 有一个完全不同的操作码,32 位和 64 位是 select 由 REX 前缀编辑的,这不会减慢解码器的速度)。所以 16b 是奇数,你应该小心使用它。优先将 16b 内存加载到 32b 变量中,以避免在使用临时变量时出现部分寄存器惩罚和 16 位立即数。 (AMD CPU 在处理 movzx
负载时效率不高(需要一个 ALU 单元和额外的 1 个周期延迟),但节省内存几乎总是值得的(出于缓存原因)。
32b 是用于局部暂存变量的 "optimal" 大小。 select 该大小不需要前缀(增加代码密度),并且在使用低位 8b 后再次使用完整寄存器时不会出现部分寄存器停顿或额外的微指令。我相信这是 int_fast32_t
类型的目的,但不幸的是,在 x86 Linux 上,该类型是 64 位。
在处理器的自然字大小(4 字节或 8 字节)上运行的原子操作的性能与在其他大小(2 字节或 1 字节)上运行的性能相比如何?
如果我需要维护一个布尔原子变量,我试图弄清楚最佳实践是什么:使用 1 字节优化 space,或使用 4/8 字节优化 (可能)优化性能。
http://agner.org/optimize/ 了解更多详情。
在 x86 上,一个 1 字节数据的数组应该不错。它可以用 movzx
(零扩展)加载,就像用普通的 mov
.
x86 具有支持原子位域的位操作,如果您想将数据打包为另一个 8 倍。不过,我不确定编译器在为这种情况编写高效代码方面做得如何。即使是只写操作也需要一个缓慢的原子 RMW 周期来处理包含您要写入的位的字节。 (在 x86 上,它会是一个 lock OR
指令,这是一个完整的内存屏障。在 Intel Haswell 上它是 8 微指令,而字节存储是 1 微指令。吞吐量的 19 倍。)这可能仍然值得如果这意味着很多缓存未命中和很少缓存未命中之间的区别,尤其是。如果大部分访问是只读的。 (读位很快,和非原子情况完全一样。)
2 字节(16 位)操作在 x86 上可能很慢,尤其是。在英特尔 CPU 上。当 Intel 指令解码器必须解码带有 16 位立即操作数的指令时,它们的速度会大大降低。这是操作数大小前缀中的 dreaded LCP stall。 (8b ops 有一个完全不同的操作码,32 位和 64 位是 select 由 REX 前缀编辑的,这不会减慢解码器的速度)。所以 16b 是奇数,你应该小心使用它。优先将 16b 内存加载到 32b 变量中,以避免在使用临时变量时出现部分寄存器惩罚和 16 位立即数。 (AMD CPU 在处理 movzx
负载时效率不高(需要一个 ALU 单元和额外的 1 个周期延迟),但节省内存几乎总是值得的(出于缓存原因)。
32b 是用于局部暂存变量的 "optimal" 大小。 select 该大小不需要前缀(增加代码密度),并且在使用低位 8b 后再次使用完整寄存器时不会出现部分寄存器停顿或额外的微指令。我相信这是 int_fast32_t
类型的目的,但不幸的是,在 x86 Linux 上,该类型是 64 位。