如何改善巨型布尔表达式的编译时间?

时间:2016-06-01 10:28:55

标签: c++ optimization compilation

我需要对矢量执行相当复杂的检查,我必须重复数千次。为了提高效率,我将给定的公式转换为C ++源代码,并在高度优化的二进制文件中进行编译,我在代码中调用它。公式总是纯粹的布尔值:只有&&,||而且!用过的。典型的源代码如下所示:

#include <assert.h>
#include <vector>

using DataType = std::vector<bool>;

static const char T = 1;
static const char F = 0;
const std::size_t maxidx = 300;

extern "C" bool check (const DataType& l);

bool check (const DataType& l) {
  assert (l.size() == maxidx);
  return (l[0] && l[1] && l[2]) || (l[3] && l[4] && l[5]); //etc, very large line with && and || everywhere
}

我按如下方式编译:

g++  -std=c++11 -Ofast -march=native -fpic -c check.cpp

生成的二进制文件的性能至关重要。

它完美地利用了最近的测试用例和大量的变量(300,如上所示)。在这个测试用例中,g ++消耗超过100 GB的内存并永远冻结。

我的问题非常简单:如何简化编译器的代码?我应该使用一些额外的变量,摆脱向量或其他东西吗?

EDIT1:好的,这是顶级实用程序的截图。

enter image description here

cc1plus忙于我的代码。 check 函数取决于584个变量(对于上面示例中的不精确数字而感到抱歉),它包含450'000个表达式。

我同意@ akakatak的评论如下。似乎g ++执行O(N ^ 2)。

2 个答案:

答案 0 :(得分:0)

这里明显的优化是抛弃向量并使用基于最快整数类型的位域:

uint_fast8_t item [n];

您可以将其写为

#define ITEM_BYTES(items) ((items) / sizeof(uint_fast8_t))
#define ITEM_SIZE(items) ( ITEM_BYTES(items) / CHAR_BIT + (ITEM_BYTES(items)%CHAR_BIT!=0) )
...
uint_fast8_t item [ITEM_SIZE(n)];

现在你有一块带有 n 段的内存,其中每个段都是CPU的理想大小。在每个这样的段中,使用按位运算符将位设置为1 = true或0 = false。

根据您希望的优化方式,您可以用不同的方式对这些位进行分组。我建议在每个段中存储3位数据,因为您总是希望检查3个相邻的布尔值。这意味着上面例子中的“n”将是布尔值的总数除以3。

然后您可以简单地遍历数组,如:

bool items_ok ()
{
  for(size_t i=0; i<n; i++)
  {
    if( (item[i] & 0x7u) == 0x7u )
    {
      return true;
    }
  }
  return false;
}

使用上述方法进行优化:

  • 进行比较的数据大小,以及可能的对齐问题。
  • 整体记忆用法。
  • 比较所需的分支数量。

这也排除了通常的C ++元编程导致的无效风险。我永远不会相信std::vectorstd::arraystd::bitfield来制作最佳代码。

完成上述工作后,您始终可以测试std::bitfield等容器是否产生完全相同的有效机器代码。如果您发现它们在您的机器代码中产生了任何形式的无关紧要的疯狂,那么请不要使用它们。

答案 1 :(得分:0)

这是一个坏死的帖子,但我仍然应该分享我的结果。

Thilo在上述评论中提出的解决方案是最好的。它非常简单,它提供了可测量的编译时间改进。只需将表达式拆分为相同大小的块即可。但是,根据我的经验,您必须仔细选择适当的子表达式长度 - 在大量子表达式的情况下,您可能会遇到显着的执行性能下降;编译器无法完美地优化整个表达式。