我前几天和朋友争论过这两个片段。哪个更快,为什么?
java.util.HashMap
和
value = 5;
if (condition) {
value = 6;
}
如果if (condition) {
value = 6;
} else {
value = 5;
}
是矩阵怎么办?
注意:我知道value
存在且我希望它更快,但它不是一个选项。
修改(工作人员要求,因为此时问题已暂停):
答案 0 :(得分:272)
TL; DR:在未经优化的代码中,String str = "Name.png";
if (str.endsWith(".")) {
System.out.println("NOT CORRECT");
} else {
System.out.println("CORRECT");
}
没有if
似乎效率更高,但即使启用了最基本的优化级别,代码也基本上被重写为{ {1}}。
我gave it a try并为以下代码生成了程序集:
else
在gcc 6.3上禁用了优化(value = condition + 5
),相关的区别是:
int ifonly(bool condition, int value)
{
value = 5;
if (condition) {
value = 6;
}
return value;
}
int ifelse(bool condition, int value)
{
if (condition) {
value = 6;
} else {
value = 5;
}
return value;
}
代表-O0
,而 mov DWORD PTR [rbp-8], 5
cmp BYTE PTR [rbp-4], 0
je .L2
mov DWORD PTR [rbp-8], 6
.L2:
mov eax, DWORD PTR [rbp-8]
代表
ifonly
后者看起来效率稍差,因为它有一个额外的跳跃,但两者至少有两个,最多三个任务,所以除非你真的需要挤压每一滴性能(提示:除非你正在航天飞机上工作你不要,即使那时你可能不这样做,差异也不会明显。
然而,即使最低优化级别(ifelse
),两个函数都会减少到相同的值:
cmp BYTE PTR [rbp-4], 0
je .L5
mov DWORD PTR [rbp-8], 6
jmp .L6
.L5:
mov DWORD PTR [rbp-8], 5
.L6:
mov eax, DWORD PTR [rbp-8]
基本上相当于
-O1
假设test dil, dil
setne al
movzx eax, al
add eax, 5
为零或一。
更高的优化级别并没有真正改变输出,除非他们设法通过在开始时有效地将return 5 + condition;
寄存器清零来避免condition
。
免责声明:您可能不应该自己编写movzx
(即使标准保证将EAX
转换为整数类型也会5 + condition
),因为您的对于阅读代码的人来说,意图可能不会立即明显(可能包括你未来的自我)。这段代码的要点是表明编译器在两种情况下产生的内容(实际上)是相同的。 Ciprian Tomoiaga在评论中说得很清楚:
人类的工作是为人类编写代码,让编译器为机器编写代码>。
答案 1 :(得分:44)
来自CompuChip的答案显示,对于int
,它们都针对同一个程序集进行了优化,因此无关紧要。
如果值是矩阵怎么办?
我将以更一般的方式解释这一点,即如果value
是一种类型,其构造和赋值很昂贵(并且移动很便宜),那该怎么办呢。
然后
T value = init1;
if (condition)
value = init2;
是次优的,因为如果condition
为真,则对init1
执行不必要的初始化,然后执行复制分配。
T value;
if (condition)
value = init2;
else
value = init3;
这样更好。但如果默认构造很昂贵,并且复制构造比初始化更昂贵,那么仍然是次优的。
你有条件运算符解决方案,这是好的:
T value = condition ? init1 : init2;
或者,如果你不喜欢条件运算符,你可以创建一个这样的辅助函数:
T create(bool condition)
{
if (condition)
return {init1};
else
return {init2};
}
T value = create(condition);
根据init1
和init2
的不同,您也可以考虑以下事项:
auto final_init = condition ? init1 : init2;
T value = final_init;
但是我必须再次强调,只有当给定类型的构造和分配真的很昂贵时,这才是相关的。即便如此,只有通过分析,你才知道。
答案 2 :(得分:11)
在伪汇编语言中,
li #0, r0
test r1
beq L1
li #1, r0
L1:
可能会或可能不会快于
test r1
beq L1
li #1, r0
bra L2
L1:
li #0, r0
L2:
取决于实际CPU的复杂程度。从最简单到最高兴:
使用大约1990年后制造的任何 CPU,良好的性能取决于instruction cache中的代码拟合。因此,如果有疑问,请尽量减少代码大小。这有利于第一个例子。
使用基本的“in-order, five-stage pipeline”CPU,这仍然是您在许多微控制器中得到的CPU,每次采用分支条件或无条件时都会有pipeline bubble,所以最小化分支指令的数量也很重要。这也有利于第一个例子。
有些更复杂的CPU - 看上去足够“out-of-order execution”,但不足以使用该概念的最着名的实现 - 可能会在遇到write-after-write hazards时产生管道泡沫。这有利于第二个示例,其中r0
无论如何只写一次。这些CPU通常足以在指令提取器中处理无条件分支,因此不会仅仅为分支惩罚交换写后写入惩罚。
我不知道是否有人还在制作这种CPU。但是,做使用无序执行的“最着名的实现”的CPU可能会在不常使用的指令上偷工减料,所以你需要意识到这种事情可以发生。一个真实的例子是false data dependencies on the destination registers in popcnt
and lzcnt
on Sandy Bridge CPUs。
在最高端,OOO引擎将最终为两个代码片段发出完全相同的内部操作序列 - 这是硬件版本“不用担心它,编译器将生成相同的机器码无论如何。“但是,代码大小仍然很重要,现在您还应该担心条件分支的可预测性。 Branch prediction个失败可能会导致完整的管道 flush ,这对性能来说是灾难性的;请参阅Why is it faster to process a sorted array than an unsorted array?了解这可以带来多大的不同。
如果分支 高度不可预测,并且您的CPU具有条件设置或条件移动指令,则可以使用它们:
li #0, r0
test r1
setne r0
或
li #0, r0
li #1, r2
test r1
movne r2, r0
条件集版本也比任何其他替代版本更紧凑;如果该指令可用,则实际上保证对于这种情况是正确的事情,即使该分支是可预测的。条件移动版本需要一个额外的临时寄存器,并且总是浪费一个li
指令的调度和执行资源;如果分支实际上是可预测的,那么分支版本可能会更快。
答案 3 :(得分:10)
在未经优化的代码中,第一个示例总是分配一次变量,有时两次。第二个例子只分配一次变量。两个代码路径上的条件相同,因此无关紧要。在优化代码中,它取决于编译器。
与往常一样,如果您有关,请生成程序集并查看编译器实际执行的操作。
答案 4 :(得分:8)
什么会让你认为他们中的任何一个甚至一个班轮更快还是更慢?
unsigned int fun0 ( unsigned int condition, unsigned int value )
{
value = 5;
if (condition) {
value = 6;
}
return(value);
}
unsigned int fun1 ( unsigned int condition, unsigned int value )
{
if (condition) {
value = 6;
} else {
value = 5;
}
return(value);
}
unsigned int fun2 ( unsigned int condition, unsigned int value )
{
value = condition ? 6 : 5;
return(value);
}
高级语言的更多代码行使编译器可以使用更多代码,因此如果您想对其进行一般规则,请为编译器提供更多代码。如果算法与上面的情况相同,那么人们会期望编译器具有最小的优化来计算出来。
00000000 <fun0>:
0: e3500000 cmp r0, #0
4: 03a00005 moveq r0, #5
8: 13a00006 movne r0, #6
c: e12fff1e bx lr
00000010 <fun1>:
10: e3500000 cmp r0, #0
14: 13a00006 movne r0, #6
18: 03a00005 moveq r0, #5
1c: e12fff1e bx lr
00000020 <fun2>:
20: e3500000 cmp r0, #0
24: 13a00006 movne r0, #6
28: 03a00005 moveq r0, #5
2c: e12fff1e bx lr
以不同的顺序执行第一个函数,但执行时间相同,这并不是什么大惊喜。
0000000000000000 <fun0>:
0: 7100001f cmp w0, #0x0
4: 1a9f07e0 cset w0, ne
8: 11001400 add w0, w0, #0x5
c: d65f03c0 ret
0000000000000010 <fun1>:
10: 7100001f cmp w0, #0x0
14: 1a9f07e0 cset w0, ne
18: 11001400 add w0, w0, #0x5
1c: d65f03c0 ret
0000000000000020 <fun2>:
20: 7100001f cmp w0, #0x0
24: 1a9f07e0 cset w0, ne
28: 11001400 add w0, w0, #0x5
2c: d65f03c0 ret
希望你明白你可以尝试过这个想法,如果不明显的是不同的实现并没有实际不同。
就矩阵而言,不确定这是多么重要,
if(condition)
{
big blob of code a
}
else
{
big blob of code b
}
只是将相同的if-then-else包装器放在大块代码周围,它们值= 5或更复杂的东西。同样地进行比较,即使它是一大堆代码,它仍然必须被计算,并且等于或不等于某些东西通常用负数编译,如果(条件)做某事通常被编译为好像不是条件goto。 / p>
00000000 <fun0>:
0: 0f 93 tst r15
2: 03 24 jz $+8 ;abs 0xa
4: 3f 40 06 00 mov #6, r15 ;#0x0006
8: 30 41 ret
a: 3f 40 05 00 mov #5, r15 ;#0x0005
e: 30 41 ret
00000010 <fun1>:
10: 0f 93 tst r15
12: 03 20 jnz $+8 ;abs 0x1a
14: 3f 40 05 00 mov #5, r15 ;#0x0005
18: 30 41 ret
1a: 3f 40 06 00 mov #6, r15 ;#0x0006
1e: 30 41 ret
00000020 <fun2>:
20: 0f 93 tst r15
22: 03 20 jnz $+8 ;abs 0x2a
24: 3f 40 05 00 mov #5, r15 ;#0x0005
28: 30 41 ret
2a: 3f 40 06 00 mov #6, r15 ;#0x0006
2e: 30 41
我们刚刚与其他人在stackoverflow上进行了这项练习。这个mips编译器很有趣,在这种情况下不仅实现了功能相同,而且只有一个函数只是跳转到另一个以节省代码空间。虽然不是这样做的,但
00000000 <fun0>:
0: 0004102b sltu $2,$0,$4
4: 03e00008 jr $31
8: 24420005 addiu $2,$2,5
0000000c <fun1>:
c: 0004102b sltu $2,$0,$4
10: 03e00008 jr $31
14: 24420005 addiu $2,$2,5
00000018 <fun2>:
18: 0004102b sltu $2,$0,$4
1c: 03e00008 jr $31
20: 24420005 addiu $2,$2,5
更多目标。
00000000 <_fun0>:
0: 1166 mov r5, -(sp)
2: 1185 mov sp, r5
4: 0bf5 0004 tst 4(r5)
8: 0304 beq 12 <_fun0+0x12>
a: 15c0 0006 mov $6, r0
e: 1585 mov (sp)+, r5
10: 0087 rts pc
12: 15c0 0005 mov $5, r0
16: 1585 mov (sp)+, r5
18: 0087 rts pc
0000001a <_fun1>:
1a: 1166 mov r5, -(sp)
1c: 1185 mov sp, r5
1e: 0bf5 0004 tst 4(r5)
22: 0204 bne 2c <_fun1+0x12>
24: 15c0 0005 mov $5, r0
28: 1585 mov (sp)+, r5
2a: 0087 rts pc
2c: 15c0 0006 mov $6, r0
30: 1585 mov (sp)+, r5
32: 0087 rts pc
00000034 <_fun2>:
34: 1166 mov r5, -(sp)
36: 1185 mov sp, r5
38: 0bf5 0004 tst 4(r5)
3c: 0204 bne 46 <_fun2+0x12>
3e: 15c0 0005 mov $5, r0
42: 1585 mov (sp)+, r5
44: 0087 rts pc
46: 15c0 0006 mov $6, r0
4a: 1585 mov (sp)+, r5
4c: 0087 rts pc
00000000 <fun0>:
0: 00a03533 snez x10,x10
4: 0515 addi x10,x10,5
6: 8082 ret
00000008 <fun1>:
8: 00a03533 snez x10,x10
c: 0515 addi x10,x10,5
e: 8082 ret
00000010 <fun2>:
10: 00a03533 snez x10,x10
14: 0515 addi x10,x10,5
16: 8082 ret
和编译器
使用此i代码可以预期不同的目标也匹配
define i32 @fun0(i32 %condition, i32 %value) #0 {
%1 = icmp ne i32 %condition, 0
%. = select i1 %1, i32 6, i32 5
ret i32 %.
}
; Function Attrs: norecurse nounwind readnone
define i32 @fun1(i32 %condition, i32 %value) #0 {
%1 = icmp eq i32 %condition, 0
%. = select i1 %1, i32 5, i32 6
ret i32 %.
}
; Function Attrs: norecurse nounwind readnone
define i32 @fun2(i32 %condition, i32 %value) #0 {
%1 = icmp ne i32 %condition, 0
%2 = select i1 %1, i32 6, i32 5
ret i32 %2
}
00000000 <fun0>:
0: e3a01005 mov r1, #5
4: e3500000 cmp r0, #0
8: 13a01006 movne r1, #6
c: e1a00001 mov r0, r1
10: e12fff1e bx lr
00000014 <fun1>:
14: e3a01006 mov r1, #6
18: e3500000 cmp r0, #0
1c: 03a01005 moveq r1, #5
20: e1a00001 mov r0, r1
24: e12fff1e bx lr
00000028 <fun2>:
28: e3a01005 mov r1, #5
2c: e3500000 cmp r0, #0
30: 13a01006 movne r1, #6
34: e1a00001 mov r0, r1
38: e12fff1e bx lr
fun0:
push.w r4
mov.w r1, r4
mov.w r15, r12
mov.w #6, r15
cmp.w #0, r12
jne .LBB0_2
mov.w #5, r15
.LBB0_2:
pop.w r4
ret
fun1:
push.w r4
mov.w r1, r4
mov.w r15, r12
mov.w #5, r15
cmp.w #0, r12
jeq .LBB1_2
mov.w #6, r15
.LBB1_2:
pop.w r4
ret
fun2:
push.w r4
mov.w r1, r4
mov.w r15, r12
mov.w #6, r15
cmp.w #0, r12
jne .LBB2_2
mov.w #5, r15
.LBB2_2:
pop.w r4
ret
现在从技术上讲,这些解决方案中存在一些性能差异,有时结果是5个案例跳过结果是6个代码,反之亦然,是一个比执行更快的分支?人们可以争辩,但执行应该有所不同。但是这更像是一个if条件vs如果没有条件在代码中导致编译器执行如果这跳过else执行通过。但这不一定是由于编码风格,而是比较以及if和else的情况,无论采用何种语法。
答案 5 :(得分:0)
好的,因为汇编是其中一个标签,我只假设您的代码是伪代码(并不一定是c),并将其转换为6502汇编。
第一个选项(没有别的)
library(ggplot2)
library(shiny)
library(plotly)
library(htmlwidgets)
ui <- basicPage(
plotlyOutput("plot1")
)
server <- function(input, output) {
p <- ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point(alpha=0) + xlim(0,5) +ylim(-3,3)
gp <- ggplotly(p)
set.seed(3)
myDF <- data.frame(X1=rnorm(10,-1), X2=rnorm(10,-1), X3=rnorm(10,-1), X4=rnorm(10,1), X5=rnorm(10,1), X6=rnorm(10,1))
colNms <- colnames(myDF)
nVar <- length(colNms)
output$plot1 <- renderPlotly({
gp %>% onRender("
function(el, x, data) {
var myDF = data.myDF
var Traces = [];
var dLength = myDF.length
var vLength = data.nVar
var cNames = data.colNms
for (a=0; a<dLength; a++){
xArr = [];
yArr = [];
for (b=0; b<vLength; b++){
xArr.push(b)
yArr.push(myDF[a][cNames[b]]);
}
var pcpLine = {
x: xArr,
y: yArr,
mode: 'lines',
line: {
color: 'orange',
width: 1
},
opacity: 0.9,
}
Traces.push(pcpLine);
}
Plotly.addTraces(el.id, Traces);
el.on('plotly_selected', function(e) {
var dLength = myDF.length
var selectedPCP = []
var xMin = e.range.x[0]
var xMax = e.range.x[1]
var yMin = e.range.y[0]
var yMax = e.range.y[1]
console.log([xMin, xMax, yMin, yMax])
var Traces = []
var drawRect = {
type: 'rect',
x0: xMin,
y0: yMin,
x1: xMax,
y1: yMax,
line: {
color: 'green',
width: 1
},
fillcolor: 'green'
}
Traces.push(drawRect);
Plotly.addTraces(el.id, Traces);
})
}", data = list(myDF = myDF, nVar = nVar, colNms = colNms))})
}
shinyApp(ui, server)
第二个选项(带别号)
ldy #$00
lda #$05
dey
bmi false
lda #$06
false brk
假设:条件在Y寄存器中,在任一选项的第一行设置为0或1,结果将在累加器中。
因此,在计算每种情况的两种可能性的周期之后,我们看到第一种结构通常更快;条件为0时为9个循环,条件为1时为10个循环,而条件为0时选项2为9个循环,条件为1时为3个循环(循环计数不包括 ldy #$00
dey
bmi else
lda #$06
sec
bcs end
else lda #$05
end brk
at结束)。
结论:BRK
比If only
构建更快。
为了完整起见,这是一个优化的If-Else
解决方案:
value = condition + 5
这会将我们的时间减少到8个周期(再次不包括最后的ldy #$00
lda #$00
tya
adc #$05
brk
)。