C++ Boost 多精度整数 uint256_t 的 sizeof 大于 32 字节

Art*_*oul 4 c++ boost x86-64 bigint boost-multiprecision

众所周知,C++ 内置类型 uint32_t、int32_t、uint64_t、int64_t 甚至 GCC/CLang 内置类型 __int128 和 unsigned __int128 的 sizeof 都恰好等于位宽除以 8。

但是,如果打印 Boost 的 sizeof boost::multiprecision::uint256_tuint512_t则 uint256_t 会得到 48 字节而不是 32 字节,uint512_t 会得到 80 字节而不是 64 字节。两种类型的 sizeof 都比预期多 16 个字节。请参阅此处的演示

但 sizeofboost::multiprecision::uint128_t恰好给出了预期的 16 个字节。

Boost 的所有整数的基类似乎cpp_int_base有几个字段:

data_type m_data;
unsigned  m_limbs;
bool      m_sign, m_internal, m_alias;
Run Code Online (Sandbox Code Playgroud)

只有 m_data 字段包含整数位,而其他字段则额外提供了 16 个不必要的 sizeof 字节。

我的问题是否可以以某种方式调整 Boost 多精度整数,使其仅包含数据位而不包含其他内容?

换句话说,它保持符号(如果是有符号整数)与Intel CPU将其保留在int64_t中相同,基本上最高位是符号位,其余位以符号补码形式编码。因此 Boost 整数的编码与默认的 intel uint64_t、int64_t 相同。

如果您查看模板的签名,cpp_int_base您会看到:

template <unsigned MinBits, unsigned MaxBits, cpp_integer_type SignType,
    cpp_int_check_type Checked, class Allocator, bool trivial = false>
struct cpp_int_base;
Run Code Online (Sandbox Code Playgroud)

显然,trivial似乎几乎做了必要的事情,cpp_int_base 与 trivial = true 的专业化仅包含两个字段:

local_limb_type m_data;
bool            m_sign;
Run Code Online (Sandbox Code Playgroud)

所以只比最小可能的 sizeof 大 1 个字节。

超过 128 位的整数默认为 trivial = true,而更大的整数默认为 trivial = false。

但是没有办法控制trivial模板参数,因为如果你查看 uint256_t 定义,你会看到:

using uint256_t = number<cpp_int_backend<256, 256,
    unsigned_magnitude, unchecked, void> >  ;
Run Code Online (Sandbox Code Playgroud)

trivial并且 cpp_int_backend的模板参数中没有任何参数,只有 cpp_int_base 内部有这个琐碎的内容。但是 cpp_int_base 对于用户来说是不可访问的,它是库的内部细节。

另外,我不知道 128 位整数如何精确地具有 16 个字节的 sizeof,因为正如我上面所示,即使是微不足道的参数也有额外的bool m_sign;字段,这应该给出额外的 1 个字节(即 17 个 sizeof)。但不知何故,128 位整数的大小并不是预期的 17 字节,而是 16 字节。

为什么我需要 boost 整数来拥有最少的位数。因为在我的程序中,我的数组中有数百万个整数。除了通常的数学之外,我还对这些整数进行自己的特殊数学运算。我的数学运算适用于通常的英特尔形式的整数,与 int64_t 和 uint64_t 的表示相同。但有时我需要常规操作,例如+ - * / % ^ | ~,而不是为了实现它们,我决定使用 Boost 多精度库。

如果 Boost 具有与 Intel 完全相同的表示形式,我就会这样做reinterpret_cast<boost::multiprecision::uint256_t &>(array[i]) *= 12345;,无需任何中间转换或 memcpy。但由于 Boost 有不同的格式,我必须来回编写自定义转换。

例如,如果我执行例如^操作,则 256 位整数需要 1-4 个 CPU 周期。如果我进行 Boost 格式的转换,则需要多花 5-10 个周期,这是一个非常大的开销。

因此,我需要这种简单的 Intel Boost 整数格式作为优化,而不是在每个操作上进行转换。

另一个不太重要的原因是,在我的模板化代码中的某个地方,我需要弄清楚给出的多个模板化类型 T 的位宽。如果这始终是一个简单的英特尔格式,那么sizeof(T) * 8将给出数字的精确位宽。对于 Boost 格式,我需要专门化一些辅助模板结构,例如BitWidthOf<T>::value.

Art*_*yer 8

来自文档

  • 当以固定精度使用时,此类型的大小始终比您预期的 N 位整数大一个机器字(加上任何编译器应用的对齐填充):额外的字存储符号和机器字数中的整数实际上正在使用中。后者是针对较大固定精度整数的优化,因此 1024 位整数具有与 128 位整数几乎相同的性能特征,而不是加法慢 4 倍,乘法慢 16 倍(假设涉及的值)总是适合 128 位)。通常,这意味着您可以使用足够宽的整数类型来应对“最坏的情况”,而性能只会略有下降,即使大多数时候实际上可以使用较窄的类型来完成算术。另请注意,小到足以容纳最大本机整数的无符号固定精度类型成为该类型的简单包装器,这包括“检查”变体。小的带符号类型总是有一个额外的符号词,因此比它们的本地等效符号大。

所以这是一种“优化”,我没有找到关闭它的方法。我会寻找替代库来完成您想要的操作(允许来自其他格式的 bit_cast)。

“琐碎”的作用是使用内置类型(如果存在),否则它会切换到原始数据后端(其中包含有关已用单词的额外信息,需要存储额外的单词)。

此外,由于本地(unsigned) __int128可用,boost::multiprecision::uint128_t可以是一个简单的包装器unsigned __int128(16 字节),但boost::multiprecision::int128_t必须保存 aunsigned __int128 m_data和 abool m_sign用于符号量值表示。


对于最后一个问题,您可以使用 获得类型的位宽std::numeric_limits<T>::digits,Boost 专门针对该类型:

template<typename T>
inline constexpr int bit_width = []{
    using lim = std::numeric_limits<T>;
    static_assert(lim::is_specialized && lim::is_integer && lim::is_bounded && lim::radix == 2);
    return lim::digits;
}();

static_assert(bit_width<std::uint16_t> == 16);
static_assert(bit_width<std::uint32_t> == 32);
static_assert(bit_width<boost::multiprecision::uint128_t> == 128);
static_assert(bit_width<boost::multiprecision::uint512_t> == 512);
static_assert(bit_width<boost::multiprecision::int512_t> == 512);
Run Code Online (Sandbox Code Playgroud)