将两个 uint8_ts 视为一个 uint16_t 效率较低

Is treating two uint8_ts as a uint16_t less efficient

假设我创建了一个 class,它的模板参数等于我想串成一个大整数的数字 uint8_t

这样我就可以像这样创建一个巨大的整数:

SizedInt<1000> unspeakablyLargeNumber;  //A 1000 byte number

现在问题来了:我是不是通过使用 uint8_ts 而不是使用更大的内置类型来降低我的速度。

例如:

SizedInt<2> num1;
uint16_t num2;

num1num2速度相同,还是num2更快?

由于减少了循环开销,您可能会从更大的类型中获得更好的性能。然而,这里的权衡是速度更快与选择大小的灵活性更低。

例如,如果您的大多数数字长度为 5 个字节,切换到 unit_16 将需要一个额外字节的开销。这意味着 20% 的内存开销。另一方面,如果我们谈论的是非常大的数字,比如 50 字节或更多,内存开销会小得多——大约 2%,因此可以以更小的成本实现速度的提高。

毫无疑问,使用 uint8_t[2] 而不是 uint16_t 会更慢。

以加法为例。为了使 uint8_t[2] 的速度达到 uint16_t 的速度,编译器必须弄清楚如何转换你的进位加法逻辑并将这些多条指令融合成一个单一的、更广泛的加法.我敢肯定,有些编译器有时能够进行此类优化,但在很多情况下,这种优化不太可能或不可能进行。

在某些架构上,这甚至适用于加载/存储,因为 uint8_t[2] 通常与 uint16_t 具有不同的对齐要求。

典型的 bignum 库,如 GMP,处理便于架构的最大单词。在 x64 上,这意味着使用 uint64_t 的数组而不是像 uint8_t 这样更小的数组。在现代微处理器上相加两个 64 位数字的速度相当快,事实上,它通常与相加两个 8 位数字的速度相同,更不用说通过小数数组传播进位位引入的数据依赖性了。这些数据依赖性意味着您通常每个时钟周期只能添加一个数组元素,因此您希望这些元素尽可能大。 (在硬件层面,有一些特殊技巧可以让进位位在整个 64 位操作中快速移动,但这些技巧在软件中不可用。)

如果您愿意,您始终可以使用模板专业化来选择合适大小的基元来制作您想要的最 space 高效的 bignums。否则,使用 uint64_t 的数组更为典型。

如果可以选择,通常最好只使用 GMP。 GMP 的某些部分是用汇编编写的,以使 bignum 操作比其他方式快得多。