这是更快的操作
Which is the faster operation?
我有两个变量a和b。我必须在变量a和b上写一个if条件:
This is First Approach:
if(a > 0 || b >0){
//do some things
}
这是第二个方法:
if((a+b) > 0){
//do some thing
}
Update:考虑a和b是无符号的。逻辑或(||)和算术(+)运算符
这个条件将围绕迭代一百万次。
对此任何帮助都将不胜感激。
你的第二个条件是错误的。如果是a=1, b=-1000
,它会求值为false
,而第一个条件会求值为true
。一般来说,您不应该担心这种测试的速度,编译器会对条件进行大量优化,因此逻辑或非常快。总的来说,比起优化这些条件,人们犯了更大的错误……所以不要尝试优化,除非你真的知道发生了什么,编译器通常比我们做得好得多。
原则上,在第一个表达式中,你有2个CMP
和一个OR
,而在第二个表达式中,你只有一个CMP
和一个ADD
,所以第二个应该更快(即使编译器在第一种情况下做一些短路,但这不能100%发生),但是在你的情况下表达式是不相等的(好吧,它们是正数…)。
我决定为C语言检查这一点,但是相同的参数适用于c++,类似的参数适用于Java(除了Java允许有符号溢出)。测试了以下代码(对于c++,将_Bool
替换为bool
)。
_Bool approach1(int a, int b) {
return a > 0 || b > 0;
}
_Bool approach2(int a, int b) {
return (a + b) > 0;
}
这导致了拆解。
.file "faster.c"
.text
.p2align 4,,15
.globl approach1
.type approach1, @function
approach1:
.LFB0:
.cfi_startproc
testl %edi, %edi
setg %al
testl %esi, %esi
setg %dl
orl %edx, %eax
ret
.cfi_endproc
.LFE0:
.size approach1, .-approach1
.p2align 4,,15
.globl approach2
.type approach2, @function
approach2:
.LFB1:
.cfi_startproc
addl %esi, %edi
testl %edi, %edi
setg %al
ret
.cfi_endproc
.LFE1:
.size approach2, .-approach2
.ident "GCC: (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]"
.section .note.GNU-stack,"",@progbits
即使考虑到现在的编译器有多聪明,这些代码也有很大的不同。为什么会这样呢?嗯,原因很简单——它们并不完全相同。如果a
是-42
, b
是2
,第一种方法将返回true
,第二种方法将返回false
。
当然,你可能认为a
和b
应该是无符号的。
.file "faster.c"
.text
.p2align 4,,15
.globl approach1
.type approach1, @function
approach1:
.LFB0:
.cfi_startproc
orl %esi, %edi
setne %al
ret
.cfi_endproc
.LFE0:
.size approach1, .-approach1
.p2align 4,,15
.globl approach2
.type approach2, @function
approach2:
.LFB1:
.cfi_startproc
addl %esi, %edi
testl %edi, %edi
setne %al
ret
.cfi_endproc
.LFE1:
.size approach2, .-approach2
.ident "GCC: (SUSE Linux) 4.8.1 20130909 [gcc-4_8-branch revision 202388]"
.section .note.GNU-stack,"",@progbits
很容易注意到approach1
在这里更好,因为它没有做无意义的加法,这实际上是完全错误的。事实上,它甚至对(a | b) != 0
进行了优化,这是正确的优化。
在C中,定义了无符号溢出,所以编译器必须处理整数非常大的情况(尝试approach2
的INT_MAX
和1
)。即使假设您知道数字不会溢出,也很容易注意到approach1
更快,因为它只是测试两个变量是否都是0
。
相信你的编译器,它会比你优化得更好,而且没有你可能不小心写出来的小bug。写代码时不要问自己i++
或++i
哪个更快,或者x >> 1
或x / 2
哪个更快(顺便说一下,x >> 1
与x / 2
对于有符号的数字做的事情不同,因为四舍五入的行为)。
如果你想优化一些东西,优化你使用的算法。而不是使用最坏情况O(N4)排序算法,使用最坏情况O(N log N)算法。这实际上会使程序更快,特别是如果您对相当大的数组进行排序
真正的答案总是两者都做,并实际测试哪一个运行得更快。这是唯一能确定的方法。
我猜第二个会运行得更快,因为添加是一个快速的操作,但是错过的分支会导致管道清理和各种讨厌的事情。但这是数据相关的。但它并不完全相同,如果a或b被允许为负或大到足以溢出,那么它就不是相同的测试。
我编写了一些快速代码并进行了反汇编:
public boolean method1(final int a, final int b) {
if (a > 0 || b > 0) {
return true;
}
return false;
}
public boolean method2(final int a, final int b) {
if ((a + b) > 0) {
return true;
}
return false;
}
这些生产:public boolean method1(int, int);
Code:
0: iload_1
1: ifgt 8
4: iload_2
5: ifle 10
8: iconst_1
9: ireturn
10: iconst_0
11: ireturn
public boolean method2(int, int);
Code:
0: iload_1
1: iload_2
2: iadd
3: ifle 8
6: iconst_1
7: ireturn
8: iconst_0
9: ireturn
如你所见,它们非常相似;唯一的区别是执行> 0
测试与a + b
;看起来||
被优化了。我不知道JIT编译器对它们进行了什么优化。
如果你想让真的挑剔:
选项1:总是1加载1比较,可能2加载2比较
选项2:总是2个加载,1个添加,1个比较
所以,哪一个表现更好取决于你的数据看起来像什么,以及是否有分支预测器可以使用的模式。如果是这样,我可以想象第一个方法运行得更快,因为处理器基本上"跳过"检查,在最好的情况下,只需要执行第二个选项的一半操作。不过,说实话,这看起来确实像是不成熟的优化,我愿意打赌,您更有可能在代码的其他地方获得更多改进。我不认为基本操作在大多数情况下是瓶颈。
两件事:
-
(a|b) > 0
是严格优于(a+b) > 0
,所以替换它
如果a
和b
有可能是负数,这两个选择是不相等的,正如@vsoftco的答案所指出的。
如果a
和b
都保证为非负整数,我将使用
if ( (a|b) > 0 )
不是if ( (a+b) > 0 )
我认为按位|
比整数加法快。
使用按位|
代替&
- 为什么在popback()操作之后,它仍然打印完整的矢量
- 重载操作程序时出错>>用于类中的字符串 memebr
- 对字符串进行位操作
- 我可以在 C++ 中的函数体之外进行操作吗?
- MPI突然停止了对多个核心的操作
- 如何在信号处理程序和普通函数中对全局变量进行互斥读写操作
- 对字符数组中的元素执行逐位操作
- 如何在directx/c++中进行平移/缩放操作
- 逐位操作的隐式类型转换
- 为什么一个向量上的多线程操作很慢
- 排序时无法执行交换操作.我做的时候它会崩溃.为什么
- 位移操作和位掩码未检测到重复字符
- 如何进行特定的位操作?
- 当我们进行一些操作时,应该使用什么'std::string'或'std::stringstream'?
- 字符串操作 - 字符计数
- 此代码中的操作流程是什么?C/C++.
- 复制和交换习惯用法与移动操作之间的交互
- 像union_这样的 Boost.Geometry 操作如何处理浮点类型的基本不精确性?
- 为什么 std::lerp 不适用于任何已实现所需操作的类型?
- 无法合并生成操作.. 先决条件不同