【问题标题】:Add, subtract and compare compressed integers加、减和比较压缩整数
【发布时间】:2018-07-26 22:09:44
【问题描述】:

我需要对等大小的小整数数组做大量简单的代数。这些操作仅包含三种类型:(i)添加数组和(ii)按元素减去数组,以及(iii)比较一个数组中的所有元素是否不小于/大于另一个数组中的对应元素。

为了提高缓存局部性和计算速度,我将每个数组的小整数逐位填充到一定数量的 64 位整数中。使用的 64 位整数的数量由分配给数组元素的位数决定。让a[j] 表示一个数组元素。我为a[j] 设计的位包括(i)可以保持最大绝对值a[j] 在计算过程中可能达到的位,(ii)符号位,以及(iii)位符号位的左边。最左边的位保存来自右边的可能进位,并在加法或减法后归零。

下面是两个 64 位整数相加、相减和比较的玩具示例,每个整数包含五个小整数:前 10 位、接下来的 5 位、接下来的 10 位、接下来的 13 位和接下来的 20 位。其余位无用,设置为0。

// leftmostBitMask = 
// 0b0111111111011110111111111011111111111101111111111111111111000000
//   ^         ^    ^         ^            ^                  
// leftmost


std::size_t add(std::size_t x, std::size_t y, std::size_t leftmostBitMask)
{
  return (x + y) & leftmostBitMask;
}


std::size_t minus(std::size_t x, std::size_t y, std::size_t leftmostBitMask)
{
  return (x - y + ((~leftmostBitMask) << 1)) & leftmostBitMask;
}


bool notAllGreaterEqual(std::size_t x, std::size_t y, std::size_t leftmostBitMask)
{
  // return (minus(x, y, leftmostBitMask) & (leftmostBitMask >> 1)) == 0;
  return (x - y) & ((~leftmostBitMask) >> 1);
}

我的算法看起来很复杂,尤其是比较函数。有更快的解决方案吗?

谢谢!

顺便说一句,SIMD 不是我所描述的。我的问题是比 SIMD 低一级的优化。

更多背景:这个想法服务于多维空间中相当复杂的搜索算法。我们观察到不同维度的值大小之间存在很大差异。例如,在计算一个重要的 6 维测试用例时,一个维度的绝对值可能达到 50000,而其他所有维度都远低于 1000。如果没有整数压缩,每个对象需要一个大小为 6 的 32 位数组,而整数压缩会减少维数为 1(64 位整数)。这种减少促使我考虑填​​充整数..

【问题讨论】:

  • 工作代码的性能问题在Code Review的范围内。
  • 您应该使用位旋转与不使用位旋转来分析您的压缩。通常,位操作会带走压缩带来的任何速度优势。让处理器用它的字长来发挥它的魔力。
  • 与其压缩,不如尝试优化。将编译器的优化设置为最大速度。接下来,组织您的数据以进行数据缓存优化。另外,尝试从数组中加载许多临时变量,然后处理它们。这将利用处理器的缓存能力。如果将临时变量声明为const,编译器可以将它们放入寄存器中,从而提高性能。
  • 这里的想法是编写代码,以便编译器可以执行最佳优化。例如,考虑循环展开。如果您为循环展开编码,编译器可能能够发出并行处理指令。
  • 顺便说一句,这被称为 SWAR,所以现在您可以搜索一些内容。已有大量工作。

标签: c++ arrays bit-manipulation


【解决方案1】:

经过仔细考虑和全面模拟,问题中列出的算法在很大程度上被过度设计了。接收进位的最左边位是不必要的。下面的代码有效:

// signBitMask = 
// 0b1000000000100001000000000100000000000010000000000000000000000000
//   ^         ^    ^         ^            ^                  
// sign bit


std::size_t add(std::size_t x, std::size_t y)
{
  return x + y;
}


std::size_t subtract(std::size_t x, std::size_t y)
{
  return x - y;
}


bool notAllGreaterEqual(std::size_t x, std::size_t y, std::size_t signBitMask)
{
  return (x - y) & signBitMask != 0;
}

这里的关键因素是对两个数组进行的每次比较都是基于 AND 的。只要x 中的一个元素整数低于y 中的对应元素,我们就要求notAllGreaterEqual() 返回true。乍一看,上面的解决方案几乎不可能是真的:当一个负元素整数与一个正元素整数相加并且结果保持正数时会发生什么?符号位必须有一个结转。在这种情况下,连续的元素整数不是被污染了吗?答案是肯定的,但没关系。集体notAllGreaterEqual() 仍将充分发挥其作用。不用按部就班地思考,用初等代数就可以很容易地证明notAllGreaterEqual() 是正确的。只有当我们想从那些 64 位缓冲区中恢复整数数组时,才会出现问题。

创建 64 位缓冲区包括 (i) 将整数转换为 std::size_t,(ii) 将整数移位预先计算的位,以及 (iii) 添加移位后的整数。如果一个整数为负数向右移动,1 必须在其左侧填充。

【讨论】:

    猜你喜欢
    • 2015-08-07
    • 1970-01-01
    • 1970-01-01
    • 2014-11-07
    • 1970-01-01
    • 2015-11-05
    • 2019-08-30
    • 2015-01-20
    • 1970-01-01
    相关资源
    最近更新 更多