11

在 32 位 CPU 上,一个整数是 4 个字节,一个短整数是 2 个字节。如果我正在编写一个使用许多数值的 C/C++ 应用程序,这些数值总是在提供的短整数范围内,使用 4 字节整数还是 2 字节整数更有效?

我听说它建议 4 字节整数更有效,因为这适合从内存到 CPU 的总线带宽。但是,如果我将两个短整数相加,CPU 是否会将这两个值并行打包一次(从而跨越总线的 4 字节带宽)?

4

8 回答 8

18

如果您有大量数字,请使用可行的最小尺寸。使用 16 位 short 数组比使用 32 位整数更有效,因为您可以获得两倍的缓存密度。与高速缓存未命中的成本相比,CPU 为处理 32 位寄存器中的 16 位值而必须进行的任何符号扩展的成本都可以忽略不计。

如果您只是在与其他数据类型混合的类中使用成员变量,那么它就不那么明确了,因为填充要求可能会消除 16 位值的任何节省空间的好处。

于 2008-10-02T16:28:09.773 回答
15

是的,您绝对应该在 32 位 CPU 上使用 32 位整数,否则最终可能会屏蔽掉未使用的位(即,它将始终以 32 位进行数学运算,然后将答案转换为 16 位)

它不会一次为您执行两个 16 位操作,但是如果您自己编写代码并且确定它不会溢出,则可以自己执行。

编辑:我应该补充一点,它在某种程度上也取决于您对“高效”的定义。虽然它能够更快地执行 32 位操作,但您当然会使用两倍的内存。

如果这些被用于某个内部循环中的中间计算,则使用 32 位。但是,如果您从磁盘读取它,或者即使您只需要为缓存未命中付费,使用 16 位整数可能仍然会更好。与所有优化一样,只有一种方法可以知道:profile it

于 2008-10-02T16:16:11.813 回答
8

如果您使用“许多”整数值,则处理中的瓶颈很可能是内存带宽。16 位整数更紧密地打包到数据缓存中,因此会带来性能优势。

如果您对大量数据进行数字运算,您应该阅读Ulrich Drepper 的每个程序员应该了解的关于内存的知识。专注于第 6 章,关于最大化数据缓存的效率。

于 2008-10-02T16:39:01.137 回答
5

32 位 CPU 是通常在内部对 32 位值进行操作的 CPU,但这并不意味着在对 8/16 位值执行相同操作时它会更慢。例如,x86 仍然向后兼容到 8086,可以对寄存器的小数部分进行操作。这意味着即使一个寄存器是 32 位宽,它也只能在该寄存器的前 16 位或前 8 位上运行,并且根本不会减速。这个概念甚至被 x86_64 采用,其中寄存器是 64 位,但它们仍然只能在前 32、16 或 8 位上运行。

此外,x86 CPU 总是从内存中加载整个缓存行,如果尚未在缓存中,并且缓存行无论如何都大于 4 字节(对于 32 位 CPU,而不是 8 或 16 字节),因此从内存中加载 2 字节的速度与从内存中加载 4 个字节。如果从内存处理许多值,16 位值实际上可能比 32 位值快得多,因为内存传输较少。如果缓存线是 8 字节,则每个缓存线有四个 16 位值,但只有两个 32 位值,因此当使用 16 位整数时,每四个值有一个内存访问,使用 32 位整数时,每两个值就有一个,导致处理大型 int 数组的传输次数增加一倍。

其他 CPU,例如 PPC,不能只处理寄存器的一部分,它们总是处理整个寄存器。然而,这些 CPU 通常具有特殊的加载操作,允许它们从内存加载 16 位值,将其扩展为 32 位并将其写入寄存器。后来他们有一个特殊的存储操作,从寄存器中取出值,只将最后 16 位存储回内存;两种操作都只需要一个 CPU 周期,就像 32 位加载/存储一样,因此也没有速度差异。而且由于 PPC 只能对寄存器执行算术运算(与 x86 不同,它也可以直接对内存进行操作),因此无论您使用 32 位整数还是 16 位整数,都会发生此加载/存储过程。

唯一的缺点是,如果您在只能对完整寄存器进行操作的 32 位 CPU 上链接多个操作,则上一个操作的 32 位结果可能必须在执行下一个操作之前“缩减”为 16 位,否则结果可能不正确。不过,这样的缩减只是一个 CPU 周期(一个简单的 AND 操作),编译器非常擅长确定何时真正需要这种缩减,以及何时将其排除在外不会对最终结果产生任何影响,所以这样的缩减并不是在每条指令之后都执行,只有在确实不可避免的情况下才会执行。一些 CPU 提供了各种“增强”指令,这使得这种削减变得不必要,我在生活中见过很多代码,我曾期望这样的削减,但查看生成的汇编代码,

So if you expect a general rule here, I'll have to disappoint you. Neither can one say for sure that 16 bit operations are equally fast to 32 bit operations, nor can anyone say for sure that 32 bit operations will always be faster. It depends also what exactly your code is doing with those numbers and how it is doing that. I've seen benchmarks where 32 bit operations were faster on certain 32 bit CPUs than the same code with 16 bit operations, however I also already saw the opposite being true. Even switching from one compiler to another one or upgrading your compiler version may already turn everything around again. I can only say the following: Whoever claims that working with shorts is significantly slower than working with ints, shall please provide a sample source code for that claim and name CPU and compiler he used for testing, since I have never experienced anything like that within about the past 10 years. There may be some situations, where working with ints is maybe 1-5% faster, yet anything below 10% is not "significant" and the question is, is it worth to waste twice the memory in some cases only because it may buy you 2% performance? I don't think so.

于 2012-08-24T18:31:07.060 回答
3

这取决于。如果您受 CPU 限制,32 位 CPU 上的 32 位操作将比 16 位快。如果您受内存限制(特别是如果您有太多 L2 缓存未命中),则使用您可以挤入的最小数据。

您可以找出您正在使用哪个分析器,该分析器将测量 CPU 和 L2 未命中,如Intel 的 VTune。您将以相同的负载运行您的应用程序 2 次,它会将 2 次运行合并到应用程序中热点的一个视图中,您可以查看每一行代码在该行上花费了多少周期。如果在昂贵的代码行中,您看到 0 次缓存未命中,则说明您受 CPU 限制。如果您看到大量未命中,则说明您的记忆力有限。

于 2008-10-02T16:34:42.817 回答
3

不要听劝告,试试看。

这可能在很大程度上取决于您正在使用的硬件/编译器。快速测试应该可以快速解决这个问题。写测试的时间可能比在这里写问题要少。

于 2009-12-05T22:58:56.367 回答
1

如果您在大型数据集上进行操作,最大的担忧是内存占用。在这种情况下,一个好的模型是假设 CPU 速度无限快,然后花时间担心必须将多少数据移入/移出内存。事实上,CPU 现在速度如此之快,以至于有时对数据进行编码(例如,压缩)效率更高。这样一来,CPU (可能)做更多的工作(解码/编码),但内存带宽大大减少。

因此,如果您的数据集很大,您最好使用 16 位整数。如果您的列表已排序,您可能会设计一个涉及差分或游程编码的编码方案,这将进一步减少内存带宽。

于 2008-10-02T16:38:12.980 回答
1

当您说 32 位时,我假设您的意思是 x86。16 位算术非常慢:操作数大小前缀使解码非常慢。所以不要让你的临时变量短 int 或 int16_t。

但是,x86 可以有效地将 16 位和 8 位整数加载到 32 位或 64 位寄存器中。(movzx / movsx:零和符号扩展)。因此,请随意对数组和结构字段使用 short int,但请确保对临时变量使用 int 或 long。

但是,如果我将两个短整数相加,CPU 是否会将这两个值并行打包一次(从而跨越总线的 4 字节带宽)?

那是胡说八道。加载/存储指令与 L1 缓存交互,限制因素是操作数;宽度无关紧要。例如,在 core2 上:每个周期 1 次加载和 1 次存储,无论宽度如何。L1 缓存有一个 128 或 256 位路径到 L2 缓存。

如果负载是您的瓶颈,那么您在加载后用轮班或掩码拆分一个广泛的负载可能会有所帮助。或者使用SIMD并行处理数据,并行加载后无需解包。

于 2009-12-05T22:52:31.433 回答