【问题标题】:compare chars for equality without branching在没有分支的情况下比较字符是否相等
【发布时间】:2013-04-15 07:06:50
【问题描述】:

previous question 上我想优化此功能:

static
lvh_distance levenshtein_distance( const std::string & s1, const std::string & s2 )
{
    const size_t len1 = s1.size(), len2 = s2.size();
    std::vector<unsigned int> col( len2+1 ), prevCol( len2+1 );

    const size_t prevColSize = prevCol.size();
    for( unsigned int i = 0; i < prevColSize; i++ )
        prevCol[i] = i;

    for( unsigned int i = 0, j; i < len1; ++i )
    {
        col[0] = i+1;
        const char s1i = s1[i];
        for( j = 0; j < len2; ++j )
        {
            const auto minPrev = 1 + std::min( col[j], prevCol[1 + j] );
            col[j+1] = std::min( minPrev, prevCol[j] + (  s1i == s2[j] ? 0 : 1   ) );

        }
        col.swap( prevCol );
    }
    return prevCol[len2];
}

一位用户评论说我可以用((s1i - s2[j]) &amp; 0x80) &gt;&gt; 7 替换s1i == s2[j] ? 0 : 1 以防止条件跳转。这个技巧是错误的,用户删除了他的评论,但我想知道是否真的有办法做到这一点。

【问题讨论】:

  • 发表评论的用户是否查看了编译器的优化输出,看看您的代码中是否真的存在条件跳转?
  • @SteveJessop 添加了其他问题的链接:是的,程序集输出显示在那里。
  • 小型局部优化是 编译器 最擅长的。我会更担心大小和数组元素的所有额外副本。 更多变量真的可以让代码更快吗?
  • @rhalbersma:感谢您的链接。不幸的是,你不能用向量做到这一点。 reserve 不会改变向量的大小,只会改变容量,因此 init 循环会越界访问。不过,您可以使用 boost::counting_iterator 或等效项来初始化向量,以避免对向量数据进行两次传递。
  • @SteveJessop 为什么不让第一个for 循环一个reserve,然后是prevCol.push_back(i)col 类似,它也可以在循环内使用push_back,之前使用reserve

标签: c++ performance optimization comparison branch-prediction


【解决方案1】:

假设代码

s1i == s2[j] ? 0 : 1

确实为您提供了您真正想避免的分支操作,您可以简单地尝试以下操作:

!(s1i == s2[j])

这应该会产生相同的效果,并且可以帮助编译器删除分支。或者,您可以颠倒逻辑并编写

s1i != s2[j]

与这种类型的优化一样,永远无法保证这会真正实现您希望的结果。优化器做了很多聪明的事情,试图预测他们对你的技巧的反应通常很困难。因此,即使在最好的情况下,您所能做的就是尝试不同的解决方案并比较生成的二进制代码。

【讨论】:

  • unsigned int,而不是 char。原则是合理的,但没有理由假设编译器会在此处生成与 OP 正在执行的代码不同的代码……
  • @KonradRudolph 谢谢。我误读了这个例子。当然,不能保证,但我之前已经看到它有预期的效果。对于这些类型的问题,永远没有任何保证。我会将其添加到我的答案中。
【解决方案2】:

为什么不使用以下代码:!(s1i == s2[j])(s1i != s2[j]),因为 bool 到 int 的转换是隐式的

【讨论】:

  • 感谢您的提示。我用它来改进我的答案,所以为你 +1。
【解决方案3】:

不是一个实际的答案,而是解决一个难题。
创建数组one_or_zero[UCHAR_MAX+1]用1填充,one_or_zero[0] = 0;
现在你可以做类似prevCol[j] + one_or_zero[s1i^s2[j]])
这将导致 s1i==s2[j] 上的 0 和 1 被添加到 prevCol[j]

【讨论】:

  • 我一定要试试。我怀疑它会更快,因为它意味着取消引用、可能有缓存未命中以及计算 XOR,但这是一种有趣的方法。
猜你喜欢
  • 2016-08-17
  • 1970-01-01
  • 1970-01-01
  • 2023-04-10
  • 1970-01-01
  • 1970-01-01
  • 2010-12-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多