【问题标题】:Optimize an array of tribools for space为空间优化一组 tribools
【发布时间】:2011-05-23 22:30:02
【问题描述】:

让我从一些背景开始:

通过“tribool”,我理解一个变量,它可以包含以下值之一:truefalsenull

在问题 Copying array of ints vs pointers to bools 中,OP 希望拥有一个尽可能小的 tribools 数组(或多或少)。

通过“一点”最基本的位符,我想出了一个解决方案,每个 tribool 使用 2 位,并允许将 OP 的 64 个 tribool 数组存储在 16 个字节中,这没关系。

我使用的 tribool 机制很简单,例如:

  • boolean A 表示“null or not null”,
  • boolean B 表示“如果不为空,则为真或假”。

但后来我想……“位”的算法定义是:

是信息量,它指定两个同样可能的事件中的哪一个将发生。

显然,真/假值是 1 位大。两个真假值作为一个整体是 2 位大。

那么我们的概念 tribool 呢?

我的观点是:就包含信息的大小而言,三元组大于 1 位但小于 2 位

  • 理由 1:假设我们如上所述实现了 if 布尔值。如果 boolean A 为“null”,则 boolean B 的值是多余的,不携带任何相关信息。
  • 理由 2:不可能将来自 2 个独立布尔值的信息存储在一个 tribool 中,因此它具有

(以上都不是正式的证明,但我相信我们可以同意triool的“大小”严格大于1位并且严格小于2。)


我的问题是:

如何以编程方式利用 tribool 的信息少于 2 位这一事实,并在软件中实现(c、c++?)一个包含 N 个 tribool 的数组对于某些 N,内存占用小于 N/4 字节?

是的,我确实理解这样的实现对硬件并不友好,并且会比任何具有冗余的常见解决方案(如 OP 的问题中提出的那些)慢。让我们只优化空间,而不是效率。

显然,这个实现需要一个与一对布尔值不同的 tribool 表示(如前所述,它本身是多余的)。理论上说有可能实现这个目标,我希望看到一个实际的实现。有什么想法吗?

【问题讨论】:

  • 空间上的优化肯定会以时间为代价。但是,是的,这是可能的。
  • 当然 :) 我最感兴趣的是解决此类问题的方法。虽然这可能不是 bit-fu 最可行的用法,但问题本身似乎很有趣,并且就在现实生活中解决类似问题的经验而言,答案将非常有价值。

标签: c++ c optimization bit-manipulation


【解决方案1】:

你的直觉是正确的,这当然是可能的。这基本上是arithmetic coding 的一种形式,或者至少是它的一个简单实例。

考虑它的最简单方法是将您的“tribools”数组编码为以 3 为底的数字 - 例如0=假,1=真,2=空。然后是以下数组:

{TRUE, FALSE, NULL, NULL, FALSE, FALSE, TRUE}

编码为数字

1022001

然后您可以以正常方式将其转换为十进制:

(1*3^0)+(0*3^1)+(0*3^2)+(2*3^3)+(2*3^4)+(0*3^5)+(1*3^6) = 946

每个 tribool 占用 ln(3)/ln(2) 位(约 1.58),因此使用此方法您可以在 32 位中存储 20 个 tribool - 因此您可以将 N=20 数组存储在 4 个字节中(其中 @ 987654326@ 是 5)。

【讨论】:

  • 这是Logisthello(奥赛罗游戏软件)用于其移动查找的编码。
  • 这确实是最紧凑的包装,因为结果数字包含的信息与原始 tribool 数组一样多。
  • 我能看到的唯一缺点是检索一个 tribool 值的复杂性(例如索引 3 处的 tribool)。这可以单独完成还是最好解码整个比特包(假设每包 32 比特)并以某种方式缓冲它?
  • @Matthieu M.:这是使用更少空间的权衡 - 检索数据更加困难:-)。但是从中间检索一个值并不难:假设我存储的数字是s,我想要位于j 位置的三元组。然后公式v=(s/(3^j))%3(其中/% 分别是整数除法和余数)将给出所需的tribool。而且,如果您将自己限制在适合机器字的 tribool 的数量(并且这样做不会浪费大量空间),那么这些操作会相当快(可能除了 ^,其中一个小查找表可以工作)。
  • @psmears:我曾希望有一个更快的公式 :) 查找表确实很容易(毕竟只有 20 个条目),也许一些按位运算会有所帮助,但它看起来确实很微-此时的优化:D
【解决方案2】:

此解决方案要求您预先知道您将拥有多少“非空”值(即在编译期间,或者您是否可以在使空间可用之前开始计算有多少非空值) .

然后您可以通过以下方式对其进行编码:

0 为空 1 表示非空,后跟 1 或 0 表示真或假。

这将导致每个 tribool 最多 2 位,如果它们都为空,则只有 1 位。

【讨论】:

    【解决方案3】:

    您可以理论上将 X 个 N 状态变量打包到

    ln(N^X) / ln M
    

    M 状态(或类似 LaTeX 表示法的 log_M (N^X))变量。对于以二进制数字存储三态变量,上面的公式变为:

    ln(3^N) / ln 2
    

    例如,在一个 8 位字节中,您可以容纳 5 个三态变量。

    当您更密集地打包变量时,解包/修改这些值会变得更加困难和缓慢。在上面的示例中,您必须重新计算整个字节才能更改单个三态变量。

    需要注意的是,一个字节用于 5 个三态变量是非常节省空间的。每个字节的密度保持不变,直到你有一个 22 个字节的包,它可以容纳 111 个三态值,而不是 110 个。但是,处理这种打包会很麻烦。

    与在一个字节中直接存储 4 个三态值相比,这些额外的工作是否值得?

    【讨论】:

      【解决方案4】:

      @psmears 是正确的,适用于所有 3 个值的可能性相同的情况。 但是,如果它们的可能性不同,或者不是独立的,如果您有足够长的字符串,您可以使用您的 2 位或任何其他编码并在其上运行 gzip。那应该将其压缩到大约理论极限。 就像在所有值都为 0 的限制中一样,它应该不会比字符串长度的对数多多少。

      顺便说一句:我们在这里谈论的是。在这种情况下,一个简单的定义是 -P(0)logP(0) - P(1)logP(1) - P(null)logP(null)。因此,例如,如果 P(0) = P(1) = 1/2,并且 P(null) = 0,则熵为 1 位。如果 P(0) = 1/2, P(1) = 1/4, P(null) = 1/4,那么熵也是 1/2 * 1 + 1/4 * 2 + 1/4 * 2 = 1 位。如果概率是 1022/1024、1/1024、1/1024,那么熵是(几乎 1)*(几乎 0)+ 10/1024 + 10/1024,大约等于 20/1024 或大约 百分之二!事情越确定,它发生的时间就越少,因此它需要的存储空间就越少。

      【讨论】:

        【解决方案5】:

        我喜欢@psmears 提出的解决方案,但它的缺点是它比直接方法慢。您可以使用稍作修改的版本,这也应该很快:

        3**5 == 243,几乎是 256。这意味着您可以轻松地将 5 个 tribool 值压缩到一个字节中。它具有相同的压缩比,但由于每个字节都是独立的,因此可以使用 LUT 来实现:

        unsigned char get_packed_tribool(unsigned char pk, int num)
        { // num = (0..4), pk = (0..242)
            return LUT[num][pk];    // 5*243 bytes of LUTs
        };
        
        unsigned char update_packed_tribool(unsigned char old_pk, int num, int new_val)
        { // new_val = 0..2
            return old_pk + (new_val - LUT[num][old_pk])*POW3_LUT[num];
        };
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-08-17
          • 2012-08-23
          • 1970-01-01
          • 2023-03-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多