【问题标题】:Fast add/subtract two integers based on sign of third integer基于第三个整数的符号快速加/减两个整数
【发布时间】:2021-02-16 15:42:29
【问题描述】:

我正在寻找一种快速(无分支)代码来计算以下内容:

if ( k > 0 )
    x += i;
else if ( k < 0 )
    x -= i;
else if ( k == 0 ) 
    // do nothing.

其中kxi 属于int 类型。

解决方案可以使用内在函数。

【问题讨论】:

  • 你破坏了编译器为此生成的低效代码?
  • 你怎么知道它不是无分支的?
  • user2052436,使用x += signof(k)*i; 的方法在i == INT_MIN, k &lt; 0 时会遭受潜在的UB。你关心这个角落案例吗?
  • 这个问题和目前发布的所有答案都是胡乱猜测。在不显示为某些 CPU 生成的机器代码的情况下讨论这个是毫无意义的。
  • @Lundin 像这样的微优化问题的本质是没有单一的最佳答案。您唯一能做的就是收集许多不同的方法,并在您自己的最终配置中对它们进行基准测试。问题中已经有一个假设,即无分支会更快,但这也不是给定的。

标签: c++ c algorithm


【解决方案1】:

除了 INT_MIN 之外,以下应该在 C++11 中工作

x += (std::signbit(-k)-std::signbit(k))*i;

以下应该适用于所有情况,包括 INT_MIN - 如果 long 比 int 长:

x += (std::signbit(-(long)k)-std::signbit(k))*i;

添加:在许多编译器内部,std::signbit 被转换为简单的移位操作,它应该是无分支且快速的。

【讨论】:

    【解决方案2】:
    x = x + i*(k>0) - i*(k<0);
    

    但是,老实说,考虑到现代编译器的许多优化可能性,我不会用代码的清晰度来换取据称更快的代码。例如,对于 x86 平台,MMX 和 SSE 单元可以在没有分支的情况下执行此操作。

    【讨论】:

      【解决方案3】:

      只是为了好玩,我提出这个:

      using opType = void(*)(int&, int);
      const opType ops[3] =
      {
          [](int&  , int  ){ },
          [](int& x, int i){ x += i; },
          [](int& x, int i){ x -= i; }
      };
      
      auto f = ops[int(k > 0) + (2 * int(k < 0))];
      f(x, i);
      

      Live Demo

      显然,仅使用数学的其他解决方案会更有效:) 但是,如果您想根据 k 进行更复杂的操作,这为您提供了一些选择。

      【讨论】:

      • 现在我很想看到这个组件:)
      • 哦,我实际上并没有要求链接,但谢谢 :) 嗯,即使在 O3 似乎也有没有得到优化的跳转。并不是说我擅长阅读汇编。
      【解决方案4】:

      尝试查看汇编语言:

      if (k != 0)
      {
          const int multiplier = (k > 0) ? 1 : -1;
          x += (multiplier) * i;
      }
      

      某些处理器可能具有仅根据条件代码执行的条件指令(如 ARM)。

      查看编译器打印的汇编代码。

      编辑 1:性能问题
      许多现代处理器的指令缓存中有足够的空间来有效地处理上述代码。对于处理器的分支决策电路,比较应该很快。

      ProfileBenchmark 在对代码必须是一行做出任何判断之前。

      【讨论】:

        【解决方案5】:

        这个answer 展示了一种快速、无分支的计算值符号的方法。假设这是正确的,你可以这样写:

        x += i * ((0 < k) - (k < 0))
        

        【讨论】:

        • 这在很大程度上取决于编译器是否具有评估0 &lt; kk &lt; 0 的无分支方式。
        • @MarkRansom 不能确定。链接的答案声称它是无分支的,我只是在此基础上构建。现在措辞更好了吗?
        【解决方案6】:

        试试这个:

        int sign = !!k - !!(k & INT_MIN) * 2;
        x += i * sign;
        

        这假定整数的二进制补码表示,导致INT_MIN 仅设置了高位。

        【讨论】:

        • @MarkRansom 添加了假设。
        • 值得注意的是,从 C++20 开始,这个假设得到了保证。
        • @eerorika C++20 是否强制要求二进制补码整数表示?这真是一个飞跃。在过去,他们总是试图适应奇怪的架构。
        • @MarkRansom 他们做到了。我不知道其他拱门是否有 C++20 实现,但如果他们想符合要求,他们将不得不模拟二进制补码。
        • 为什么不用~INT_MAX 替换INT_MIN 而忘记它是否是二进制补码?
        【解决方案7】:

        这是一个无分支的解决方案,但时间会因您的环境而异。它不应该超过 0.005 秒,所以我认为这不是问题。

        k>0?x+=i:k<0?x-=i:0
        

        【讨论】:

        • 三元运算符类似于分支或if 语句。
        【解决方案8】:

        您似乎期望“无分支”的代码可以衡量且重要的是“更快”?启用最大优化设置后,您的编译器现在生成的指令序列到底是什么?然后您是否分析该代码以确认它不够快?

        【讨论】:

        • 虽然这是一个公平的问题,但我觉得这更像是一个评论而不是一个答案。
        【解决方案9】:

        只需使用良好的优化设置编译您的代码,反汇编您的代码,然后查看结果。然后更改您的代码,直到反汇编显示您想要的。您有可能已经获得了无分支代码。如果你写的话会有更好的机会

        if ( k > 0 )
            x += i;
        if ( k < 0 )
            x -= i;
        

        因为中间的 else 使得编译器更难避免分支。

        【讨论】:

          猜你喜欢
          • 2013-01-12
          • 2016-05-08
          • 2013-02-04
          • 1970-01-01
          • 2012-07-31
          • 2015-08-13
          • 2014-02-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多