【问题标题】:Way to optimize a condition "if (m == 0 || n == 0)"优化条件“if (m == 0 || n == 0)”的方法
【发布时间】:2014-07-16 19:32:44
【问题描述】:

我正在编写一个函数,这就是我目前所拥有的:

template <typename T> 
M2SA(T* A, size_t m, T* B, size_t n)
{
    /* Require both arrays be nonempty */
    if (m == 0 || n == 0)
    {
        throw ("Cannot find median of 2 sorted arrays if either is empty!";)
    }
}

有什么方法可以优化条件if (m == 0 || n == 0) 使用位比较操作???

【问题讨论】:

  • 过早的优化是万恶之源,[Donald Knuth]。
  • @40two:哇,你是怎么知道OP的名字的?!
  • 如果条件检查是瓶颈,那么我敢打赌函数调用是一个更大的瓶颈......
  • 这是一个棘手的问题,因为已排序数组和空数组的中位数显然是已排序数组的中位数。
  • 为什么要使用位运算?为什么不考虑利用云的解决方案?

标签: c++ algorithm optimization bit-manipulation


【解决方案1】:

有什么办法可以优化条件if (m == 0 || n == 0)(使用位操作)???

对于几乎所有平台来说,答案都是否定的,除非 mn 极有可能为 0 并且参数未在寄存器中传递:
如果您要求优化,编译器将生成最佳代码,很可能类似于以下内容:

(load first argument into register)
(load second argument into register)
logical-and both arguments (not bitwise-and)
jump to throw statement if the zero-flag set

两个测试的两条指令! AFAIK,没有哪个平台不是最佳的。
有一种情况,编译器可以进一步优化:如果它内联函数,它可以传播常量,这可能使条件成为常量。

无论如何,您代码中的异常消息表明您正在测试错误的条件,您想要!m &amp;&amp; !n
几乎相同的 cmets 适用。

【讨论】:

    【解决方案2】:

    在更改一行代码之前,将编译器的优化设置设置为高并查看汇编语言。

    我看不出如何优化表达式:((m == 0) || (n == 0)) 并从中挤出可忽略不计的时间。数据高速缓存未命中或指令高速缓存重新加载与执行这两个子表达式的性能相同或更慢。

    这是扩展的含义(不一定是最佳的):

    if (m == 0)
    {
      throw (/*...*/);
    }
    else  // m != 0
    {
      if (n == 0)
      {
        throw (/*...*/);
      }
    }
    

    最好的解决方案是分支指令最少的解决方案。

    扩展版的汇编语言如下:

    ; optional:  move m into register 0
      compare register 0 to zero.
      branch, if equal, to Throw.
    ; optional:  move n into register 1
      compare register 1 to zero.
      branch if not equal to Continue
    Throw:
      call throw_mechanism;
    Continue:
    

    为指令计数建立一个真值表

      m   |  n    |  instructions processed
    ------+-------+-------------------
      0   |  N/A  |  2
     !0   |  0    |  4
     !0   |  != 0 |  4
    

    m 的前两条指令总是被处理:获取和评估。 m != 0 导致获取和评估 4 条指令的情况。

    所以平均和最坏的情况是执行了 2 条额外的指令。

    假设每条指令的指令处理速率为 50 纳秒,那么您在最好的情况下节省了 100 纳秒,最坏的情况是使用 200 纳秒。

    如前所述,数据缓存命中或指令流水线(缓存)的重新加载可能需要超过 200 纳秒(最坏的时间情况)。

    总结
    因为对给定表达式的优化产生的结果可以忽略不计,并且只适用于一小部分代码,所以它被称为微优化。如果mn 不在数据缓存中或必须从内存中加载,则从内存中获取这两个值所需的时间可能等于或大于处理额外指令所需的时间,用完任何通过优化表达式获得的时间。同样,如果需要重新加载指令流水线或缓存。通过优化这个表达式节省的时间被浪费在研究优化的时间或其他等待外部实体(如鼠标点击或硬盘驱动器 I/O)的代码上。

    换句话说,您和您的程序将受益于使您的代码更健壮和正常工作,而不是担心像这样的微不足道的优化。

    【讨论】:

    • 也值得谈谈语义缺陷:throw ("Cannot find median of 2 sorted arrays if either is empty!";)
    【解决方案3】:

    更正错误的答案。

    降低分支和指令计数的一种方法是使用按位与:

    if ( 0 == n * m )
    {
        ...
    }
    

    乘法速度很快,如果放在一个简单的循环中,编译器可以很容易地对其进行向量化。 生成的程序集包含 3 条指令,包括跳转。

    MUL a,b // assume a and b are registers
    TEST a
    JNZ after_the_if_scope
    //throw code
    
    after_the_if_scope:
    ...
    

    话虽如此,结果的统计信息(真或假)对性能的影响更大,因为在许多情况下,分支错误预测的成本高于实际计算。

    另请注意,如果 mm 不是纯整数而是昂贵的函数调用,则应保持逻辑或避免评估第二个函数。

    【讨论】:

    • 小心!由于历史原因,== 的优先级高于 &amp;
    • 另外,a deleted answer 上的评论与您在这个问题上的评论非常相似,这是正确的:“m = 1n = 3 呢?”
    • 总是在深夜回答时发生。我更正了我的答案。
    • 更好!但是如果m = n = ((size_t)1) &lt;&lt; (sizeof(size_t) * CHAR_BIT / 2)呢?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-30
    • 2015-02-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多