【问题标题】:Initializing very large C++ std::bitset at compile time在编译时初始化非常大的 C++ std::bitset
【发布时间】:2021-07-21 16:50:22
【问题描述】:

我想存储一个 216 位的静态常量位集,其中包含永不改变的 1 和 0 的特定序列。

我想过使用this post 提出的初始化字符串:

std::bitset<1<<16> myBitset("101100101000110 ... "); // the ellipsis are replaced by the actual 65536-character sequence

但是编译器 (VS2013) 给了我"string too long" 错误。

更新

我尝试按照上面链接的帖子中的建议将字符串分成更小的块,如下所示:

std::bitset<1<<16> myBitset("100101 ..."
                            "011001 ..."
                            ...
                            );

但我收到错误C1091:编译器限制:字符串长度超过 65535 个字节。我的字符串是 65536 字节(技术上是 65537,带有 EOS 字符)。

我还有哪些其他选择?

更新

感谢luk32,这是我最终得到的漂亮代码:

const std::bitset<1<<16> bs = (std::bitset<1<<16>("101011...")
    << 7* (1<<13)) | (std::bitset<1<<16>("110011...")
    << 6* (1<<13)) | (std::bitset<1<<16>("101111...")
    << 5* (1<<13)) | (std::bitset<1<<16>("110110...")
    << 4* (1<<13)) | (std::bitset<1<<16>("011011...")
    << 3* (1<<13)) | (std::bitset<1<<16>("111011...")
    << 2* (1<<13)) | (std::bitset<1<<16>("111001...")
    << 1* (1<<13)) | std::bitset<1<<16>("1100111...");

【问题讨论】:

  • 你不能把它分成多行吗?你知道myBitset("1011" "0010" 在两个中间引号之间有一个实际的换行符(不是文字的一部分)吗?
  • 您是否从您链接的问题中尝试this answer
  • 我没有,但如果没有其他选择,我会分开。
  • 现在我有,我更新了帖子。
  • 为什么不将其转储为二进制数据,将其作为二进制数据重新加载,并使用位掩码和偏移量从中获取数据? bitset 的功能有限。

标签: c++ initialization large-data compile-time-constant std-bitset


【解决方案1】:

您并没有真正拆分文字。无论如何,它都会被连接起来进行编译。您受到编译器的限制。我认为没有办法在 MSVC 中增加此限制。

您可以将其拆分为两个字面量,初始化两个位集,将第一个部分和 OR 与另一个进行移位。

类似:

#include <iostream>
#include <string>
#include <bitset>

 
using namespace std;
int main()
{
    std::bitset<8> dest("0110");
    std::bitset<8> lowBits("1001");

    dest <<= dest.size()/2;
    dest |= lowBits;
    std::cout << dest << '\n';
}

如果您查看clang compiler output at -02,它会优化为加载105,即01101001

我的测试表明,如果你将8 换成1&lt;&lt;16,它会使用 SSE,所以它应该是相当安全的选择。它没有像816 那样丢弃文字,因此可能会有一些运行时开销,但我不确定您是否可以做得更好。

编辑:

我又做了一些测试,这里是my playground

#include <iostream>
#include <string>
#include <bitset>
 

using namespace std;
int main()
{
    //static const std::bitset<16> set1( "01100110011001100110011001100110");
    static const std::bitset<16> set2(0b01100110011001100110011001100110);

    static const std::bitset<16> high(0b01100110);
    static const std::bitset<16> low (0b01100110);
    static const std::bitset<16> set3 = (high << 8) | low;
    std::cout << (set3 == set2) << '\n';
}

我无法在除 clang 之外的任何编译器上对 const char* 构造函数进行编译时优化,并且最多可以使用 14 个字符。如果你做了一堆似乎有一些承诺bitsets 从 unsigned long long 初始化并移位并将它们组合在一起:

static const std::bitset<128> high(0b0110011001100110011001100110011001100110011001100110011001100110);
static const std::bitset<128> low (0b1001100110011001100110011001100110011001100110011001100110011001);
static const std::bitset<128> set3 = (high << high.size()/2) | low;
std::cout << set3 << '\n';

这使得编译器坚持二进制数据存储。如果可以使用带有constexpr 的更新编译器,我认为可以将其声明为由ulls 构造的bitsets 数组,并通过constexpr 函数将它们连接起来并绑定到@987654342 @ 变量,它应该确保可能的最佳优化。编译器仍然可能对你不利,但没有理由。也许即使没有constexpr,它也会生成非常优化的代码。

【讨论】:

  • 问题是关于 MSVC,而你的 Godbolt 链接是关于 Clang,它在优化方面比 MSVC 更新和更好
  • 这有什么变化?你希望我改进什么?该解决方案在每个编译器上都可以正常工作。 clang 并不比 msvc 更新。新的clang比旧的msvc新,是的。这很明显。我没有提出任何未经证实的说法,优化部分是一个附录,它也说明它非常有限。我可以将其更改为“在我测试的每个编译器上使用 bitset 的大小 > 14 导致 tuntime 初始化”但请注意,OP 询问的是如何编译它,而不是如何获得超级优化。
  • 通过阅读您所写的内容,人们会认为 MSVC 会这样优化,甚至使用 SSE,但事实并非如此。至少你必须在 Godbolt 中将编译器更改为 MSVC
  • 不使用SSE?为什么不?无论如何,我添加了一个关于我的摆弄的结论,并明确声明了从const char* 初始化。不幸的是,低于 19 的 MSVC 在 Godbolt 上不可用,甚至可用版本的程序集对我来说都是乱码。我认为结果不会有太大不同。我不相信 VC13 编译器在那些优化方面比旧的 clang 和 gcc 更差。
  • 因为自动矢量化仅在 VS2015 之后才真正可用,尽管 VS2013 中有一些初步的自动矢量化支持,并且代码生成仅在 VS2017 中使用新的 SSA 编译器得到显着改进。 2015 年之前的所有 MSVC 版本都很糟糕,甚至 VS2019 的输出通常仍然落后于 gcc 和 Clang。您可以在x86 标签中看到很多比较
【解决方案2】:

您可以考虑完全跳过编译,简单地说:

  • 将数据组装成一个目标文件(段.rodata),导出它的符号及其大小。
  • .h 文件中将这些符号声明为extern const
  • 使用这些符号并将您的程序链接到此目标文件。

我没有方便的 MASM32 来编写一个实际有效的完整答案,但我经常将这种技术与 GAS 和 LD 一起使用,它可以解决很多问题。 (按需加载、单独数据文件的安全描述符、极快的编译时间……)

请注意,这就是 VS 资源编译器所做的,简而言之……因此您可以将数据作为资源包含并获取指向它的指针。

【讨论】:

    【解决方案3】:

    不可能拥有这样的静态std::bitset,因为:


    如果允许在 runtime 进行构造,则只需将字符串文字拆分为多个小于 2048 个字符的较小字符串,以防总长度小于 65536:

    ANSI 兼容性要求编译器在串联后接受最多 509 个字符的字符串文字。 Microsoft C 中允许的字符串文字的最大长度约为 2,048 字节。但是,如果字符串文字由用双引号括起来的部分组成,则预处理器会将这些部分连接成一个字符串,并且对于连接的每一行,它会在总字节数中添加一个额外的字节。

    [...]

    虽然单个带引号的字符串不能超过 2048 个字节,但可以通过连接字符串来构造大约 65535 个字节的字符串字面量。

    https://docs.microsoft.com/en-us/cpp/c-language/maximum-string-length?view=msvc-160

    如前所述,较长的字符串必须手动连接。这里

    const int LENGTH = 1 << 16;
    std::bitset<LENGTH> myBitset(
        "100101 ..."  // 2ᴺ bits
        "011001 ..."  // 2ᴺ bits
        ...
        "001011 ...", // must be one shorter than the previous lines: 2ᴺ⁻¹ bits
        LENGTH - 1    // size
    );
    myBitset[LENGTH - 1] = 1; // set the final bit
    

    或者只使用数组而不是字符串文字:

    static const char BITSET[LENGTH] = {
        '1', '0', '0', '1',...
        ...
        '0', '1', '0', '0'
    };
    std::bitset<LENGTH> myBitset(BITSET, sizeof(BITSET));
    

    【讨论】:

    • 为什么你认为上面的问题和constexpr有关系?
    • 从很早以前就有static bitset&lt;&gt; 很有可能(在gcc 4.4 上使用-std=c++0x)。很难通过预编译的二进制序列对其进行初始化。例如。 clang 将初始化字符串优化到 14,gcc 不 static 只是表示存储。您可以在运行开始时初始化static const bitsetconstexpr 可以提供帮助,但也不能保证。
    • @luk32 问题是关于 MSVC,以及一个非常古老的 MSVC 版本
    • @ClaasBontus 为什么你认为“编译时初始化”与 constexpr 无关?
    • @phuclv 因为无论如何编译器都不会强制使用预计算值进行初始化。不过,它可能会有所帮助。 constexpr 只是意味着可以在编译时评估函数,它仍然可以将逻辑留给运行时。另一方面,static 并没有改变任何东西,您的回答表明您不能拥有static bitset,这是错误的。你不能有 bitset like that 即在编译时初始化。但是你不能使用任何编译器(除了我指出的异常)。 constexprstatic 在这方面没有任何改变。
    猜你喜欢
    • 1970-01-01
    • 2014-05-02
    • 2021-04-22
    • 1970-01-01
    • 1970-01-01
    • 2017-12-31
    • 2011-01-11
    • 1970-01-01
    相关资源
    最近更新 更多