【问题标题】:C++ 2.5 bytes (20-bit) integerC++ 2.5 字节(20 位)整数
【发布时间】:2010-09-16 09:25:32
【问题描述】:

我知道这很荒谬,但我需要它来优化存储。有什么好的方法可以在 C++ 中实现它吗?

它必须足够灵活,以便我可以将其用作普通数据类型,例如Vector< int20 >、运算符重载等。

【问题讨论】:

  • 磁盘或内存存储优化?
  • 您通常需要多少这些值?
  • @liori: in-memory, @peterchen: 我需要很多...
  • 很多答案表明内存在 c++ 中总是对齐的,真的是这样吗?你不能告诉编译器按照你想要的方式对齐吗?
  • @rubenvb:是的,它是对齐的(这不是 C++ 特定的)。是的,可以指示大多数编译器以自定义方式对齐数据,但这会破坏 ABI 并因此与其他所有库(您没有源代码)兼容。

标签: c++ integer operator-overloading cuda byte


【解决方案1】:

如果存储是您主要关心的问题,我怀疑您需要相当多的 20 位变量。如何将它们成对存储?您可以创建一个代表两个此类变量的类并将它们存储在 2.5+2.5 = 5 个字节中。

为了方便地访问变量,您可以覆盖 [] 运算符,以便您可以编写:

int fst = pair[0];
int snd = pair[1];

因为您可能希望允许诸如

之类的操作
pair[1] += 5;

您不想返回支持字节的副本,而是返回一个引用。但是,您不能返回对支持字节的直接引用(因为它会弄乱它的相邻值),因此您实际上需要返回一个支持字节的代理(反过来具有对支持字节的引用)并让代理重载相关运算符。

事实上,正如@Tony 所建议的那样,您可以将其概括为拥有一个包含 N 个这样的 20 位变量的通用容器。

(我自己在一个专门化的向量中完成了这项工作,以有效地存储布尔值(作为单个位)。)

【讨论】:

  • 我之前想过这个解决方案。但仍然不知道如何表示它,以便轻松执行 a+b 或 Vector .. 之类的操作
  • 一旦你开始考虑代理,在我看来你最好拥有一个任意数量的 20 位值的自定义容器和一个代理,而不需要中间的 pair 表示使事情复杂化.
  • 布尔向量已经专门(由 STL 开发人员)用于以位存储值:)
  • @n0rd: 大多数人都同意这是一个错误 :-)
  • @Matthieu:它是vector 的特化是错误的,因为它并不能真正满足vector 甚至sequence 的要求。带迭代器的变长bitset的原理没有错。
【解决方案2】:

不...您不能将其作为单个值语义类型执行...任何类数据都必须是 8 位字符大小的倍数(邀请所有关于 CHAR_BITS 等的常见讽刺)。

也就是说,让我们抓住稻草......

不幸的是,您显然要处理很多数据项。如果这超过 64k,则任何放入打包值的自定义容器中的代理对象也可能需要 >16 位的索引/句柄,但仍然是我认为值得进一步考虑的少数可能性之一。如果您只是在某个时间点积极使用并需要一小部分值的值语义行为,那么它可能是合适的。

struct Proxy
{
    Int20_Container& container_;  // might not need if a singleton
    Int20_Container::size_type index_;
    ...
};

因此,代理可能是 32 位、64 位或更多位 - 潜在的好处是只有当您可以从索引到容器中动态创建它们,让它们直接写回容器中,并保持它们的生命周期很短与少数同时。 (实现此模型的一种简单方法(不一定是最快的)是使用 STL 位集或向量作为 Int20_Container,并在 index_ 中存储 20 倍的逻辑索引,或者动态乘法。)

虽然您的值范围超过 20 位空间,但您在实际使用中的不同值还不到 64k。如果您对数据集有一定的了解,您可以创建一个查找表,其中 16 位数组索引映射到 20 位值。

【讨论】:

  • “值语义类型”?这是否排除了位域或其他内容?
  • @Potatoswatter:不,位域具有值语义。这意味着在复制、分配、运算符==、超出范围等时,类型的行为与您期望的一样,例如 int 或 std::string。指向值的语义。如果有兴趣,请参阅parashift.com/c++-faq-lite/value-vs-ref-semantics.html
  • 好吧,既然位域不必是 8 位的倍数,也许你应该改变答案。
  • 我想我已经失去了你:-)。问题:“它必须足够灵活,以便我可以将其用作普通数据类型,例如 Vector、运算符重载等。”。这是另一种说法,它需要值语义。您可以使用 20 位字段,但它不会阻止包含它的对象被填充到 32 位。
【解决方案3】:

使用类。只要尊重copy/assign/clone/etc...STL语义,就不会有任何问题。

但它不会优化您计算机上的内存空间。特别是如果你放入一个平面数组,20bit 可能会在 32bit 边界上对齐,所以 20bit 类型的好处是没有用的。

在这种情况下,您需要定义自己的优化数组类型,它可以与 STL 兼容。但不要指望它会很快。不会的。

【讨论】:

    【解决方案4】:

    使用位域。 (我真的很惊讶没有人提出这个建议。)

    struct int20_and_something_else {
        int less_than_a_million : 20;
        int less_than_four_thousand : 12; // total 32 bits
    };
    

    这仅适用于结构中元素的相互优化,您可以在其中使用其他一些数据来填补空白。但是效果很好!

    如果你真的需要优化一个由 20 位数字组成的巨大数组,而没有别的,那就是:

    struct int20_x3 {
        int one : 20;
        int two : 20;
        int three : 20; // 60 bits is almost 64
    
        void set( int index, int value );
        int get( int index );
    };
    

    如果你愿意,你可以添加getter/setter函数让它更漂亮,但你不能获取位域的地址,它们也不能参与数组。 (当然,你可以有一个struct的数组。)

    用作:

    int20_x3 *big_array = new int20_x3[ array_size / 3 + 1 ];
    
    big_array[ index / 3 ].set( index % 3, value );
    

    【讨论】:

    • 发帖者基本上是想要int20_t data[HUGE_NUM]。你提供Twenty_Twelve data[HUGE_NUM]。这不会节省内存,也不会允许X n = data[20] + data[50]; my_vector_of_X.push_back(n)。所有的“十二”将用于什么?我们中的一个人很困惑,因为我看不出这有什么关系。
    • @Tony:我提供了两种解决方案。一个将 20 位存储在一个易于寻址的结构中(假设还有其他数据碎片,这通常是这种情况),另一个将 3x20 位存储在一个需要将数组索引除以 3 的结构中。真的,你可以不知道这是如何解决问题的?
    • 不,我不能。它基本上就像 aioobe 的解决方案。客户端代码必须在各处提供索引 % 3 值,这会阻止您根据离散值语义变量进行思考。 .set 不是“=”。必须放 x.get(2) + x.get(1) 与 x + y 不同。您的建议如何真正改进直接和重复索引到单个“Container big_array”?
    • @Tony:除了我提供了一个可以立即使用的具体解决方案。额外的抽象可以消除所有语法上的丑陋,但是 C 提供了框架,并且位域已经是一系列位的有限代理对象。在实际使用中,大多数人选择像我的回答那样处理代码,因为功能齐全的代理比几个内部循环中的额外击键更麻烦。无论如何,说不可能是不正确的。
    • @Potatoswatter:好点子 - 你有一个简单的工作和可行的解决方案在几行中,而价值语义代理可能更像 50 行,在这种情况下必然会起作用权衡,如果由 STL 位容器支持,自定义容器可能有 20 行。这里没有很好的答案,我对你的答案没有任何问题,只是整个问题是关于争取那些价值语义,检查我们能走多远以及付出什么代价。
    【解决方案5】:

    您可以使用 C++ std::bitset。将所有内容存储在 bitset 中并使用正确的索引 (x20) 访问您的数据。

    【讨论】:

      【解决方案6】:

      您将无法获得恰好 20 位的类型(即使是有点打包的结构),因为它总是与一个字节对齐(以最小的粒度)。如果你必须有 20 位,Imo 唯一的方法是创建一个比特流来处理数据(你可以重载以接受索引等)

      【讨论】:

        【解决方案7】:

        您可以使用union 关键字来创建位字段。当位域是必需品时,我已经使用过它。否则,您可以创建一个包含 3 个字节的类,但通过按位运算只公开最重要的 20 个字节。

        【讨论】:

        • 但是2个字节只是16位,如何从中获得20位。
        • WTF?一个 2 字节的值只能容纳 16 位(假设 8 位字节)。
        • 对不起,意思是 3 个字节。已更正。
        • 但是它需要 3 个字节的存储空间......我最终使用了 24 位整数
        • 所以使用联合...(提示:它仍然会花费您 3 个字节,甚至可能花费 4 个字节,因为编译器将内存分配对齐在 4 字节块中 - 当然取决于编译器和建筑)。但是你为什么这么在意?您正在为什么环境开发,4 位是非常宝贵的?
        【解决方案8】:

        据我所知,这是不可能的。

        最简单的选择是定义一个自定义类型,它使用 int32_t 作为后备存储,并实现适当的数学作为覆盖运算符。

        为了获得更好的存储密度,您可以在单个 int64_t 值中存储 3 个 int20。

        【讨论】:

          【解决方案9】:

          只是一个想法:使用优化存储(两个实例 5 个字节),对于操作,将其转换为 32 位 int 然后再返回。

          【讨论】:

            【解决方案10】:

            虽然可以通过多种方式做到这一点。 一种可能是使用位旋转将它们存储为 5 字节数组的左右部分,并带有一个要存储/检索的类,该类将您所需的数组条目转换为 byte5[] 数组中的数组条目并提取左边或右边减半。

            但是,在大多数硬件上,整数都需要字对齐,并且需要旋转位来提取整数,您需要进行一些位移位才能正确对齐。

            我认为增加交换空间并让虚拟内存处理大型阵列会更有效(毕竟 20 与 32 并没有多大的节省!)总是假设你有一个 64 位操作系统。

            【讨论】:

            • 20 vs 32 节省了 37.5%。如果他的程序在通过这个阵列时受到内存带宽的限制,他几乎可以得到那么大的速度提升。我认为速度快了 37%。
            猜你喜欢
            • 1970-01-01
            • 2018-03-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-04-10
            • 1970-01-01
            • 2019-03-23
            • 2011-06-27
            相关资源
            最近更新 更多