C++ 位压缩结构

C++ 位压缩结构,c++,performance,bit-manipulation,bit-fields,C++,Performance,Bit Manipulation,Bit Fields,我目前正在从事这个项目,我需要在vector中存储相当多的结构(几十亿个单位)。我还需要以线性方式对向量进行迭代,所以我需要通过的数据越少越好 因此我自然开始优化单体结构的尺寸。例如,如果我有多个bool值,我可以将真/假值存储在一个位中,并将所有bool值压缩到一个char/16位中,只要大小足够。对于某些条目,我只需要20位无符号整数。因此,我可以再次压缩这些值 然后我得到了类似的结果(请注意,这只是一个简化的示例): class-Foo{ 私人: uint32分钟; uint32_t m_

我目前正在从事这个项目,我需要在vector中存储相当多的结构(几十亿个单位)。我还需要以线性方式对向量进行迭代,所以我需要通过的数据越少越好

因此我自然开始优化单体结构的尺寸。例如,如果我有多个bool值,我可以将真/假值存储在一个位中,并将所有bool值压缩到一个char/16位中,只要大小足够。对于某些条目,我只需要20位无符号整数。因此,我可以再次压缩这些值

然后我得到了类似的结果(请注意,这只是一个简化的示例):

class-Foo{
私人:
uint32分钟;
uint32_t m_梳;
公众:
Foo(uint32、uint32、uint16、bool是蓝色的、bool是漂亮的)

:m_time(t),m_comb((大您可以使用非常轻松地实现这一点)

class Foo {
private:
    uint32_t m_time;
    uint32_t m_big : 20;
    uint32_t m_small : 10;
    uint32_t m_isblue : 1;
    uint32_t m_isnice : 1;
public:
    Foo(uint32_t t, uint32_t big, uint16_t small, bool is_blue, bool is_nice)
        : m_time(t), m_big(big), m_small(small), m_isblue(is_blue), m_isnice(is_nice)
    { }

    uint32_t get_time()  const { return m_time; }
    uint32_t get_big()   const { return m_big; }
    uint16_t get_small() const { return m_small; }
    uint16_t is_blue()   const { return m_isblue; }
    uint16_t is_nice()   const { return m_isnice; }
};
显示大小

编辑:其他信息

为了总结注释,编译器将位字段打包在一起的方式取决于实现:

9.6/1(…)类对象内的位字段分配由实现定义。位字段对齐由 实现已定义。位字段打包到一些可寻址 分配单元。[注:在某些情况下,位字段跨越分配单元 机器上,而不是其他机器上。位字段在上从右向左分配 一些机器,从左到右在其他机器上。-结束注释]


因此,您无法保证,但编译器通常会尽力将它们放在一起。根据经验,只要您的位字段是连续的,并且它们的总位小于您正在使用的基类型,那么打包就很有可能是最佳的。如果需要,您可以微调基类型,如图所示。

因为位field是
实现定义的
我决定编写位读/写函数。当您想在磁盘上存储数据(缓存或其他东西)时,它会很方便

这里是源代码模拟


您可以将这些函数转换为
#定义
,并在调试模式下添加范围检查。

对于其中的一些,您可以使用,也许对于其他位。人们有时也会使用
枚举
与两个枚举一起计算。我建议探索它们,看看剩下的内容是否值得深入模板。哇“我还需要以线性方式迭代向量,所以我需要遍历的数据越少越好。”还将重构的代码<>代码>数组> <代码>结构> <代码>,这样说:对于每一个字段,使用它自己的容器,并相应地优化容器。这有助于优化数据缓存的使用,如果您需要在该字段中扫描该数组以获得特定的值。这也允许使用<代码> FFS。()
要在一位字段数组中搜索。@在这种情况下,Dummy00001、20和10位整数需要自定义数组专门化。@bipll,是的,它们需要。这是一个优势-如果您需要将数据结构扩展到数以百万计的项。您不仅可以计算每一位,还可以推测地使用稀疏结构,以避免大量的零浪费内存。另外,这允许使用另一个技巧:将数据表示从
index->value
转换为
value->set of index
。需要做更多的工作,但是如果内存消耗/访问是优先考虑的,那么这是值得的。很好。我可以假设,这通常会存储位,而不存储任何数据吗例如,我将定义8x<代码> uTn3xt某个名称:20 < /代码>,这只占用20字节吗?@ jordas。不幸的是,没有。字段将填充<代码> UTIT32×T ,但是当添加不止一个元素时,它会启动一个新的元素。换句话说,一个位字段不能在两个代码> UITT32×T< < /C>元素之间延伸。nts。这是不幸的。但很容易理解。不过,如果有这样的解决方案就好了。@Jendas标准状态为“*类对象中的位字段分配是实现定义的*”。因此您没有正式的保证,但通常编译器会按预期打包它们(如Lindydancer所述,对未使用的位进行填充)。所有这些功能都是为了在内存不足的情况下优化内存使用而设计的。请注意:类对象中位字段的实际分配详细信息都是
实现定义的
。因此,您不必为其他程序存储它。
class Foo {
private:
    uint32_t m_time;
    uint32_t m_big : 20;
    uint32_t m_small : 10;
    uint32_t m_isblue : 1;
    uint32_t m_isnice : 1;
public:
    Foo(uint32_t t, uint32_t big, uint16_t small, bool is_blue, bool is_nice)
        : m_time(t), m_big(big), m_small(small), m_isblue(is_blue), m_isnice(is_nice)
    { }

    uint32_t get_time()  const { return m_time; }
    uint32_t get_big()   const { return m_big; }
    uint16_t get_small() const { return m_small; }
    uint16_t is_blue()   const { return m_isblue; }
    uint16_t is_nice()   const { return m_isnice; }
};
#include <iostream>

bool GetBit(uint32_t num, uint8_t pos)
{
    //check pos < sizeof(num)*8
    return (num >> pos) & 0x1;
}

uint8_t GetByte(uint32_t num, uint8_t pos)
{
    //check pos < sizeof(num)*8
    return (num >> pos) & 0xFF;    
}

uint16_t GetShort(uint32_t num, uint8_t pos)
{
    //check pos < sizeof(num)*8
    return (num >> pos) & 0xFFFF;    
}

uint32_t SetBit(bool val, uint32_t &dest, uint8_t pos)
{
    dest ^= (-val ^ dest) & (1 << pos);
    return dest;
}

uint32_t SetByte(uint8_t val, uint32_t &dest, uint8_t pos)
{
    dest &= ~(0xFF<<pos); //clean
    dest |= val<<pos; //set
    return dest;
}

uint32_t SetShort(uint16_t val, uint32_t &dest, uint8_t pos)
{
    dest &= ~(0xFFFF<<pos); //clean
    dest |= val<<pos; //set
    return dest;
}

void PrintBin(uint32_t s, const char* pszComment)
{
    std::cout << pszComment << ": " << std::endl;
    for (size_t n = 0; n < sizeof(s) * 8; n++)
        std::cout << GetBit(s, n) << ' ';
    std::cout << std::endl;
}

int main()
{
    uint32_t s = 4294967295; //all bits
    PrintBin(s, "Start");

    SetBit(false, s, 2); 
    PrintBin(s, "Set bit 2 to FALSE");

    SetByte(0, s, 22);
    PrintBin(s, "Set byte 22 to val 0");

    SetByte(30, s, 22);
    PrintBin(s, "Set byte 22 to val 30");

    SetShort(0, s, 4);
    PrintBin(s, "Set short 4 to val 0");

    SetShort(65000, s, 4);
    PrintBin(s, "Set short 4 to val 65000");

    std::cout << "byte at 22 = " << (int)GetByte(s, 22) << std::endl;
    std::cout << "short at 4 = " << (int)GetShort(s, 4) << std::endl;    
}
Start: 
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
Set bit 2 to FALSE: 
1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 
Set byte 22 to val 0: 
1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 1 1 
Set byte 22 to val 30: 
1 1 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 1 1 
Set short 4 to val 0: 
1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1 1 1 1 0 0 0 1 1 
Set short 4 to val 65000: 
1 1 0 1 0 0 0 1 0 1 1 1 1 0 1 1 1 1 1 1 1 1 0 1 1 1 1 0 0 0 1 1 
byte at 22 = 30
short at 4 = 65000