int32_t的延迟是否低于int8_t、int16_t和int64_t

Does int32_t have lower latency than int8_t, int16_t and int64_t?

本文关键字:int16 int64 int8 延迟 是否 int32      更新时间:2023-10-16

(我指的是Intel CPU,主要是GCC,但拥有ICC或MSVC(

使用int8_tint16_tint64_tint32_t相比效率较低,这是真的吗,因为生成了额外的指令来在 CPU 字大小和所选变量大小之间进行转换?

如果有人对此有任何示例或最佳实践,我会很感兴趣?我有时会使用较小的变量大小来减少缓存行负载,但假设我只消耗了 50 字节的缓存行,其中一个变量是 8 位 int,使用剩余的缓存行空间并将 8 位 int 提升为 32 位 int 等可能会更快?

您可以将更多的 uint8_t s 填充到缓存行中,因此加载 N uint8_t s 将比加载 N uint32_t s 更快。

此外,如果您使用的是带有 SIMD 指令的现代英特尔芯片,智能编译器将尽可能矢量化。同样,在代码中使用小变量将允许编译器将更多通道填充到 SIMD 寄存器中。

我认为最好使用最小的尺寸,并将细节留给编译器。当涉及到这样的事情时,编译器可能比你(和我(更聪明。对于许多操作(例如无符号加法(,编译器可以使用相同的代码进行uint8uint16uint32(并且只是忽略高位(,因此没有速度差异。

最重要的是,缓存

未命中比任何算术或逻辑运算都昂贵得多,因此担心缓存(以及数据大小(几乎总是比简单的算术更好。

(很久以前,在Sun工作站上,使用doublefloat快得多,因为硬件只支持double。我认为现代 x86 不再如此,因为 SIMD 硬件(SSE 等(直接支持单精度和双精度(。

Mark Lakata的回答指向正确的方向。我想补充几点。

理解和做出优化决策的绝佳资源是 Agner 文档。

指令

表文档具有最常见指令的延迟。您可以看到其中一些在本机大小版本中表现更好.
例如,可以消除movmul具有较少的延迟.
然而,这里我们谈论的是获得 1 个时钟,我们必须执行大量指令来补偿缓存未命中.
如果这就是整个故事,那就不值得了。

真正的问题来自解码器。当您使用一些长度变化的前缀(您将使用非本机大小的单词(时,解码器需要额外的周期。

因此,操作数大小前缀会更改指令其余部分的长度。预解码器无法在单个时钟周期内解决此问题。从此错误中恢复需要 6 个时钟周期。因此,避免使用这种长度变化的前缀非常重要。

如今,在不再更新(但仍然存在(的微拱中,惩罚是严厉的,特别是使用某种算术指令.
在后来的微拱中,这已经减轻了,但惩罚仍然存在。

要考虑的另一个方面是,使用非本机大小需要为指令添加前缀,从而生成更大的代码。这是尽可能接近声明">生成附加指令以在 CPU 字大小和所选变量大小之间进行转换",因为英特尔 CPU 可以处理非本机字大小。
对于其他,特别是RISC,CPU,这通常不是正确的,可以生成更多的指令。

因此,在优化使用数据缓存的同时,也对指令缓存进行了不良利用

同样毫无价值的是,在通用的 x64 ABI 上,堆栈必须在 16 字节边界上对齐,并且通常编译器以本机字大小或接近的字大小保存本地变量(例如 64 位系统上的 DWORD(.
仅当您分配足够数量的本地变量或使用数组或打包结构时,您才能从使用小变量大小中获得好处.
如果你声明一个uint16_t var,它可能会占用与单个uint64_t相同的堆栈空间,所以最好选择最快的大小。

此外,当涉及到数据缓存时,重要的是位置,而不仅仅是数据大小。

那么,该怎么办呢?

幸运的是,您不必在拥有小数据或小代码之间做出决定。

如果您有大量数据,则通常使用数组或指针以及使用中间变量进行处理。一个例子是这行代码。

t = my_big_data[i];

我的方法是:

  • 保持数据的外部表示形式,即my_big_data数组,尽可能小。例如,如果该数组存储温度,则对每个元素使用编码uint8_t

  • 保持数据的内部表示形式,即t变量,尽可能接近 CPU 字大小。例如,t可以是uint32_tuint64_t

通过这种方式,您可以编程优化缓存并使用本机字大小.
作为奖励,您以后可能会决定切换到 SIMD 指令,而无需重新打包my_big_data内存布局。

<小时 />

真正的问题是程序员花了太多时间在错误的地方和错误的时间担心效率;过早的优化是编程中所有邪恶(或至少大部分(的根源><。D. 高德纳

当你设计你的结构时,内存布局是问题驱动的。例如,年龄值需要 8 位,以英里为单位的城市距离需要 16 位.
编写代码时,算法使用编译器已知具有的该范围的最快类型。例如,整数比浮点数快,uint_fast8_t不比uint8_t慢。

什么时候需要提高性能,首先通过更改算法(通过使用更快的类型、消除冗余操作等(,然后如果需要数据结构(通过对齐、填充、打包等(。