我正在编写一个函数,这是我到目前为止所做的:
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)
???
答案 0 :(得分:13)
有什么方法可以优化条件
if (m == 0 || n == 0)
(使用位操作)???
除非m
或n
非常可能为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 && !n
。
几乎相同的评论适用。
答案 1 :(得分:8)
在更改一行代码之前,请将编译器的优化设置设置为高并查看汇编语言。
我看不出你如何优化表达式:((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纳秒(最差时间情况)。
<强>摘要强>
因为给定表达式的优化产生可忽略的结果并且适用于非常小的代码部分,所以它被称为微优化。如果m
或n
不在数据高速缓存中或必须从内存加载,则从内存中获取这两个值所需的时间可能等于或大于处理额外指令所需的时间,通过优化表达式获得任何时间。类似地,如果需要重新加载指令管道或缓存。通过优化此表达式节省的时间在研究优化或等待外部实体的其他代码(例如鼠标单击或硬盘驱动器I / O)时被浪费。
换句话说,您和您的程序将从使代码更健壮和正常工作中获益更多,而不是担心像这样的琐碎优化。
答案 2 :(得分:0)
更正了错误答案。
降低分支和指令计数的方法是使用按位AND:
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:
...
话虽如此,结果的统计数据(真或假)会更多地影响性能,因为在许多情况下,分支误预测的成本高于实际计算。
另请注意,如果m
和m
不是普通整数,而是昂贵的函数调用,则应保持逻辑或避免评估第二个函数。