在32位CPU上,整数占4个字节,短整数占2个字节。如果我编写一个使用许多数字值的C/C++应用程序,这些数字值始终适合于提供的短整数范围内,那么使用4字节整数还是2字节整数更有效率呢?
有人建议使用4字节整数更有效率,因为它符合从内存到CPU的总线带宽。但是,如果我将两个短整数相加,CPU是否会在单次传输中并行处理这两个值(从而跨越总线的4个字节带宽)?
在32位CPU上,整数占4个字节,短整数占2个字节。如果我编写一个使用许多数字值的C/C++应用程序,这些数字值始终适合于提供的短整数范围内,那么使用4字节整数还是2字节整数更有效率呢?
有人建议使用4字节整数更有效率,因为它符合从内存到CPU的总线带宽。但是,如果我将两个短整数相加,CPU是否会在单次传输中并行处理这两个值(从而跨越总线的4个字节带宽)?
如果你有一个大数组,那么就选择最小的适用尺寸。使用16位的short数组比32位的int数组更加高效,因为可以获得两倍的缓存密度。与在32位寄存器中使用16位值所需的任何符号扩展相比,CPU必须执行的成本微不足道,而缓存未命中的成本则非常高。
如果你只是在类中混合使用成员变量和其他数据类型,那么情况就不太清楚了,因为填充要求很可能会消除16位值的任何节省空间的好处。
在32位CPU上,您应该使用32位整数,否则它可能会遮盖未使用的位(即它将始终以32位进行计算,然后将答案转换为16位)。
它不会为您执行两个16位操作,但如果您自己编写代码并且确信它不会溢出,您可以自己执行操作。
编辑:我应该补充说明,这也在某种程度上取决于您对“高效”的定义。虽然它能够更快地执行32位操作,但您当然会使用两倍的内存。
如果这些用于某个内部循环中的中间计算,则应使用32位。 然而,如果您从磁盘读取此内容,或者即使只需要支付缓存未命中的代价,仍然可能更好地使用16位整数。与所有优化一样,只有一种方法可以知道:进行性能分析。
int_fast16_t
和32位,使它们成为许多事情的糟糕选择。可能设计师们考虑的是不需要为数组索引进行符号扩展,但在Ice Lake之前,Intel上的64位除法速度较慢,而在许多早期的x86-64 CPU(包括AMD)上,64位乘法速度也较慢。请参见为什么在x86_64中uint_least16_t比uint_fast16_t更快进行乘法运算?。 - Peter Cordesax
再尝试从eax
读取数据,会出现“部分寄存器停顿”的情况。这是需要注意的问题。 - cHaomovsx
,movzx
)。因此,您可以完全运行32位程序,避免停顿,并仍然使用16位值。 - cHao不要听别人的建议,试试看。
这可能与您使用的硬件/编译器密切相关。一个快速测试可以轻松解决这个问题。可能花费的时间比在这里写问题还要少。
当你说32位时,我会假设你指的是x86。16位算术运算非常慢:操作数大小前缀使解码变得非常慢。因此,不要将临时变量设置为short int或int16_t。
然而,x86可以有效地将16位和8位整数加载到32位或64位寄存器中(movzx / movsx:零扩展和符号扩展)。因此,可以放心地在数组和结构体字段中使用short int,但请确保在临时变量中使用int或long。
然而,如果我正在将两个短整数相加,CPU是否会在单个并行传递中打包这两个值(从而跨越总线的4字节带宽)?
那是无稽之谈。加载/存储指令与L1缓存交互,限制因素是操作数的数量;宽度是无关紧要的。例如,在core2上:每个周期1次加载和1次存储,无论宽度如何。 L1缓存具有到L2缓存的128位或256位路径。
如果加载是瓶颈,则可以使用一次宽加载,在加载后使用移位或掩码进行拆分。或者使用SIMD在加载并行处理数据而无需在并行加载后解包。
如果你正在处理大型数据集,最大的问题就是内存占用。在这种情况下,一个好的模型是假设CPU速度无限快,并花费时间担心需要移动多少数据到/从内存中。事实上,现在的CPU速度非常快,有时候编码(例如压缩)数据更加高效。这样,CPU会做更多的工作(解码/编码),但内存带宽大大降低。
因此,如果你的数据集很大,最好使用16位整数。如果你的列表已排序,你可以设计一种差分或游程编码的编码方案,这将进一步减少内存带宽。