在 javascript 分支

Branching in javascript

考虑这段代码:

if (a) 
    doSomething(a)
else if (b)
    doSomething(b)
else
    doSomething(c)

我可以使用 javascript 逻辑运算符将其重写为:

(a && doSomething(a) || (!a && b && doSomething(b)) || (!a && !b && doSomething(c)))

我知道它不是很可读,但这会以某种方式优化以前的版本吗?

因为operator&&||return的实际表达式值,会不会比较少?

你更需要考虑的是可读性。您需要找到优化(速度、内存使用等)和可读性之间的界限。

通常情况下,这些类型的优化会产生较小的结果(微优化),但会大大阻碍可读性。

您可以像本例中那样用 true1 的 return 分隔每个评估部分。如果 abc 之一为真,则结束检查。

a && (evaluate(a), 1) || b && (evaluate(b), 1) || evaluate(c)

一个更短的版本适用于数组,因为数组是真实的。

a && [evaluate(a)] || b && [evaluate(b)] || evaluate(c)

我很好奇 运行 通过 v8 反汇编器 (d8 --print_code) 这两个功能(简化)。

第一个版本:

--- Raw source ---
(a, b) {
    if (a) {
        return foo(a);
    } else if (b) {
        return bar(b);
    }
}


--- Code ---
source_position = 47
kind = FUNCTION
name = x
Instructions (size = 284)
 44 REX.W movq rax,[rbp+0x18]
 48 call 0x235a8cb18560     ;; debug: statement 60
                            ;; debug: position 64
                            ;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 8)
 53 REX.W testq rax,rax
 56 jz 184  (0x235a8cb66518)
 62 REX.W movq rcx,0x1d21fad2d781    ;; object: 0x1d21fad2d781 <String[3]: foo>
 72 REX.W movq rdx,[rsi+0x27]
 76 call 0x235a8cb368c0     ;; debug: statement 77
                            ;; debug: position 84
                            ;; code: contextual, LOAD_IC, UNINITIALIZED
 81 push rax
 82 REX.W movq r10,0x144d04104121    ;; object: 0x144d04104121 <undefined>
 92 push r10
 94 push [rbp+0x18]
 97 REX.W leaq rdx,[r12+r12*2]
101 REX.W movq rdi,[rsp+0x10]
106 call 0x235a8cb15d80     ;; code: CALL_IC, DEFAULT
111 REX.W movq rsi,[rbp-0x8]
115 REX.W addq rsp,0x8
119 REX.W movq rbx,0x7ed0fc04b11    ;; object: 0x7ed0fc04b11 Cell for 6144
129 addl [rbx+0xb],0xd1
133 jns 166  (0x235a8cb66506)
135 push rax
136 call InterruptCheck  (0x235a8cb3ac60)    ;; code: BUILTIN
141 pop rax
142 REX.W movq rbx,0x7ed0fc04b11    ;; object: 0x7ed0fc04b11 Cell for 6144
152 REX.W movq r10,0x180000000000
162 REX.W movq [rbx+0x7],r10
166 REX.W movq rsp,rbp      ;; debug: statement 141
                            ;; js return
169 pop rbp
170 ret 0x18
173 int3
174 int3
175 int3
176 int3
177 int3
178 int3
179 jmp 266  (0x235a8cb6656a)
184 REX.W movq rax,[rbp+0x10]
188 call 0x235a8cb18560     ;; debug: statement 103
                            ;; debug: position 107
                            ;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 24)
193 REX.W testq rax,rax
196 jz 266  (0x235a8cb6656a)
202 REX.W movq rcx,0x1d21fad2d7a1    ;; object: 0x1d21fad2d7a1 <String[3]: bar>
212 REX.W movq rdx,[rsi+0x27]
216 call 0x235a8cb368c0     ;; debug: statement 120
                            ;; debug: position 127
                            ;; code: contextual, LOAD_IC, UNINITIALIZED
221 push rax
222 REX.W movq r10,0x144d04104121    ;; object: 0x144d04104121 <undefined>
232 push r10
234 push [rbp+0x10]
237 xorl rdx,rdx
239 REX.W leaq rdx,[rdx+r12*4]
243 REX.W movq rdi,[rsp+0x10]
248 call 0x235a8cb15d80     ;; code: CALL_IC, DEFAULT
253 REX.W movq rsi,[rbp-0x8]
257 REX.W addq rsp,0x8
261 jmp 119  (0x235a8cb664d7)
266 REX.W movq rax,[r13-0x58]
270 jmp 119  (0x235a8cb664d7)
275 nop

"Optimized"版本:

--- Raw source ---
(a, b) {
  return a && foo(a) || (!a && b && bar(b))
}

--- Code ---
source_position = 154
kind = FUNCTION
name = y
Instructions (size = 372)

 44 REX.W movq rax,[rbp+0x18]
 48 push rax
 49 call 0x235a8cb18560     ;; debug: statement 165
                            ;; debug: position 172
                            ;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 13)
 54 REX.W testq rax,rax
 57 jnz 69  (0x235a8cb66705)
 63 pop rax
 64 jmp 130  (0x235a8cb66742)
 69 REX.W addq rsp,0x8
 73 d0000 REX.W movq rcx,0x1d21fad2d781    ;; object: 0x1d21fad2d781 <String[3]: foo>
 83 REX.W movq rdx,[rsi+0x27]
 87 call 0x235a8cb368c0     ;; debug: position 177
                            ;; code: contextual, LOAD_IC, UNINITIALIZED
 92 push rax
 93 40000 REX.W movq r10,0x144d04104121    ;; object: 0x144d04104121 <undefined>
103 push r10
105 push [rbp+0x18]
108 REX.W leaq rdx,[r12+r12*2]
112 REX.W movq rdi,[rsp+0x10]
117 call 0x235a8cb15d80     ;; code: CALL_IC, DEFAULT
122 REX.W movq rsi,[rbp-0x8]
126 REX.W addq rsp,0x8
130 push rax
131 call 0x235a8cb18560     ;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 9)
136 REX.W testq rax,rax
139 jz 151  (0x235a8cb66757)
145 pop rax
146 jmp 296  (0x235a8cb667e8)
151 REX.W addq rsp,0x8
155 REX.W movq rax,[rbp+0x18]
159 call 0x235a8cb18560     ;; debug: position 189
                            ;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 35)
164 REX.W testq rax,rax
167 jnz 179  (0x235a8cb66773)
173 REX.W movq rax,[r13-0x40]
177 jmp 183  (0x235a8cb66777)
179 REX.W movq rax,[r13-0x38]
183 push rax
184 call 0x235a8cb18560     ;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 31)
189 REX.W testq rax,rax
192 jnz 204  (0x235a8cb6678c)
198 pop rax
199 jmp 212  (0x235a8cb66794)
204 REX.W addq rsp,0x8
208 REX.W movq rax,[rbp+0x10]
212 push rax
213 call 0x235a8cb18560     ;; debug: position 194
                            ;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 27)
218 REX.W testq rax,rax
221 jnz 233  (0x235a8cb667a9)
227 pop rax
228 jmp 296  (0x235a8cb667e8)
233 REX.W addq rsp,0x8
237 REX.W movq rcx,0x1d21fad2d7a1    ;; object: 0x1d21fad2d7a1 <String[3]: bar>
247 REX.W movq rdx,[rsi+0x27]
251 call 0x235a8cb368c0     ;; debug: position 199
                            ;; code: contextual, LOAD_IC, UNINITIALIZED
256 push rax
257 REX.W movq r10,0x144d04104121    ;; object: 0x144d04104121 <undefined>
267 push r10
269 push [rbp+0x10]
272 xorl rdx,rdx
274 REX.W leaq rdx,[rdx+r12*4]
278 REX.W movq rdi,[rsp+0x10]
283 call 0x235a8cb15d80     ;; code: CALL_IC, DEFAULT
288 REX.W movq rsi,[rbp-0x8]
292 REX.W addq rsp,0x8
296 REX.W movq rbx,0x7ed0fc04b31    ;; object: 0x7ed0fc04b31 Cell for 6144
306 addl [rbx+0xb],0xd1
310 jns 343  (0x235a8cb66817)
312 push rax
313 call InterruptCheck  (0x235a8cb3ac60)    ;; code: BUILTIN
318 pop rax
319 REX.W movq rbx,0x7ed0fc04b31    ;; object: 0x7ed0fc04b31 Cell for 6144
329 REX.W movq r10,0x180000000000
339 REX.W movq [rbx+0x7],r10
343 REX.W movq rsp,rbp      ;; debug: statement 207
                            ;; js return

即使你不读 x86 汇编程序,也很明显 "optimized" 版本实际上有 more 比较和 more 分支(寻找 jmpjz 等)。

所以你问题的答案

will this somehow be optimizing the previous version?

不是,至少v8是这样(我猜其他引擎在这方面差别不大)。

这是foo(a || b || c)的dis:

--- Raw source ---
(a, b, c) {
    return foo(a || b || c);
}


--- Code ---
source_position = 220
kind = FUNCTION
name = z
Instructions (size = 228)
 44 REX.W movq rcx,0x36f71e52d781    ;; object: 0x36f71e52d781 <String[3]: foo>
 54 REX.W movq rdx,[rsi+0x27]
 58 call 0x117d9bb368c0     ;; debug: statement 236
                            ;; debug: position 243
                            ;; code: contextual, LOAD_IC, UNINITIALIZED
 63 push rax
 64 REX.W movq r10,0xb18ce704121    ;; object: 0xb18ce704121 <undefined>
 74 push r10
 76 REX.W movq rax,[rbp+0x20]
 80 push rax
 81 call 0x117d9bb18560     ;; debug: position 247
                            ;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 19)
 86 REX.W testq rax,rax
 89 jz 101  (0x117d9bb66945)
 95 pop rax
 96 jmp 109  (0x117d9bb6694d)
101 REX.W addq rsp,0x8
105 REX.W movq rax,[rbp+0x18]
109 push rax
110 call 0x117d9bb18560     ;; debug: position 252
                            ;; code: TO_BOOLEAN_IC, UNINITIALIZED (id = 15)
115 REX.W testq rax,rax
118 jnz 131  (0x117d9bb66963)
124 REX.W addq rsp,0x8
128 push [rbp+0x10]
131 REX.W leaq rdx,[r12+r12*2]
135 REX.W movq rdi,[rsp+0x10]
140 call 0x117d9bb15d80     ;; code: CALL_IC, DEFAULT
145 REX.W movq rsi,[rbp-0x8]
149 REX.W addq rsp,0x8
153 REX.W movq rbx,0xdf65a404b41    ;; object: 0xdf65a404b41 Cell for 6144
163 addl [rbx+0xb],0xd1
167 jns 200  (0x117d9bb669a8)
169 push rax
170 call InterruptCheck  (0x117d9bb3ac60)    ;; code: BUILTIN
175 pop rax
176 REX.W movq rbx,0xdf65a404b41    ;; object: 0xdf65a404b41 Cell for 6144
186 REX.W movq r10,0x180000000000
196 REX.W movq [rbx+0x7],r10
200 REX.W movq rsp,rbp      ;; debug: statement 261
                            ;; js return
203 pop rbp
204 ret 0x20
207 int3
208 int3
209 int3
210 int3
211 int3
212 int3
213 REX.W movq rax,[r13-0x58]
217 jmp 153  (0x117d9bb66979)
222 nop

这个问题在评论中几乎已经得到了回答,但我想扩展一些评论并提供一些额外的见解。

正如 Frédéric Hamidi 在评论中指出的那样,一个好的 JavaScript JIT 编译器可能会修复代码的两个示例,并产生完全相同的结果。但是,让我们假设没有提供优化的 JIT 编译器,而是假设您只有一个按顺序执行命令的解释器。

在那种情况下,在您的第一个示例中(使用 if-elseif-else),您将执行以下步骤(请原谅粗略的汇编伪代码,但我认为它说明了问题):

 1. CMP a, 0 // compare value of a to zero
 2. JZ 5 // If comparison is zero (a and 0 are equivalent), jump to the address of the else-if (starts on 5) instruction
 3. CALL doSomething(a) // Not how you would pass a parameter in assembly, but we'll skip over that stuff as it is irrelevant
 4. JMP 10 // Jump to end line. We do not need to do other evaluations.
 5. CMP b, 0 // Compare value of b to zero
 6. JZ 9 // If comparison is zero, jump to the else instruction (line 9)
 7. CALL doSomething(b)
 8. JMP 10 // Jump to end line. We do not need to do other evaluations.
 9. doSomething(c) // Else, we do something to C
 10. RET // Return/exit. We are finished.

另一方面,让我们看一下第二个代码示例(仅使用布尔运算的代码示例)的序列:

 1. CMP a, 0
 2. JZ 6 // Start of comparison #2
 3. CALL doSomething(a)
 4. CMP EAX, 0 // Let's assume the call to doSomething puts a result in EAX
 5. JNZ 23 // Jump to end if doSomething returned a "truthy" result. Line 23 is the function's return point
 6. NOT a // let's say this call puts the NOTed a in EDX register
 7. CMP EDX, 0
 8. JZ 14  // start of comparison #3
 9. CMP b, 0
 10. JZ 14 // start of comparison #3
 11. CALL doSomething(b)
 12. CMP EAX, 0
 13. JNZ 23 // Again, jumping to return if doSomething returned "truthy" value.
 14. NOT a
 15. CMP EDX, 0
 16. JZ 23
 17. NOT b
 18. CMP EDX, 0
 19. JZ 23
 20. CALL doSomething(c)
 21. CMP EAX, 0
 23. RET

我要继续说示例代码 #1(使用 if-elseif-else 分支)可能会更有效率。同样,正如在对你的问题的评论中提到的,一个好的 JIT 编译器可能会将两个代码示例优化到等效状态,但是如果你只使用解释器,没有任何类型的编译器来优化代码,那么你的第二个代码示例将需要更多操作,因为要检查的条件和变量更多。

注意: 这里的汇编绝不是100%准确的,它实际上是伪代码而不是正确的汇编。我只是简单地包含它以指出每个代码示例必须执行的操作数量的差异。

调查

 (a && doSomething(a) || (!a && b && doSomething(b)) || (!a && !b && doSomething(c)))

表明它不太可能是最优的。

a 为真但 doSomething returns 为假值(0、假、未定义、空)的情况为例。在这种情况下,a && doSomething(a) 为假,使表达式求值继续 !a && b,它立即为假(a 为真,使 !a 为假),这使表达式求值继续 !a && !b 这也是错误的。

因此,例如,盲目执行重写的代码可能会导致 a 被测试三次,而使用 if 测试的代码只需要测试 a 一次。