为什么 `-1 * x` 比 `-x` 快,为什么?
Why is `-1 * x` faster than `-x` and why?
使用此代码:
include Benchmark
n = 10**8
r = []
Benchmark.benchmark(" "*7 + CAPTION, 7, FORMAT, ">avg:", ">total:") do |b|
a = 1
r << b.report("Benchmark -1:") { (n).times do
-1 * a
end }
r << b.report("Benchmark - :") { (n).times do
-a
end }
[(r.sum{|e| e }) / 2, r.sum{|e| e }]
end
我在 ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-linux]
得到了这个结果:
user system total real
Benchmark -1: 4.930000 0.000000 4.930000 ( 4.938359)
Benchmark - : 5.650000 0.000000 5.650000 ( 5.667566)
>avg: 5.290000 0.000000 5.290000 ( 5.302962)
>total: 10.580000 0.000000 10.580000 ( 10.605924)
这看起来违反直觉,因为如果我猜的话,我会赌“-x”而不是“-1*x”更快。
为什么不同?还是我的测量存在重大缺陷?
如果您查看生成的字节码,可能会有所帮助。如果你把它简化为像
这样简单的东西
puts RubyVM::InstructionSequence.compile(<<-CODE
a = 1
-1 * a
CODE
).to_a.join("\n")
puts RubyVM::InstructionSequence.compile(<<-CODE
a = 1
-a
CODE
).to_a.join("\n")
您还可以使用 #~$ ruby --dump insns -e 'code'
打印指令序列(如 @Stefan 所述),这实际上会输出更清晰的输出。
#~$ ruby --dump insns -e 'a = 1; -a'
== disasm: <RubyVM::InstructionSequence:<main>@-e>======================
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] a
0000 trace 1 ( 1)
0002 putobject 1
0004 setdynamic a, 0
0007 trace 1
0009 getdynamic a, 0
0012 send :-@, 0, nil, 0, <ic:0>
0018 leave
#~$ ruby --dump insns -e 'a = 1; -1 * a'
== disasm: <RubyVM::InstructionSequence:<main>@-e>======================
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] a
0000 trace 1 ( 1)
0002 putobject 1
0004 setdynamic a, 0
0007 trace 1
0009 putobject -1
0011 getdynamic a, 0
0014 opt_mult <ic:1>
0016 leave
您会在字节码列表中注意到,在 -1 * a
Ruby 中执行了一个 opt_mult
,这基本上只是一个算术运算,并在内部进行了优化。
另一方面,-a
位是一个 opt_send_without_block
,这是一个实际的方法调用。我猜这就是第二个版本(有点)慢的原因。
使用此代码:
include Benchmark
n = 10**8
r = []
Benchmark.benchmark(" "*7 + CAPTION, 7, FORMAT, ">avg:", ">total:") do |b|
a = 1
r << b.report("Benchmark -1:") { (n).times do
-1 * a
end }
r << b.report("Benchmark - :") { (n).times do
-a
end }
[(r.sum{|e| e }) / 2, r.sum{|e| e }]
end
我在 ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-linux]
得到了这个结果:
user system total real
Benchmark -1: 4.930000 0.000000 4.930000 ( 4.938359)
Benchmark - : 5.650000 0.000000 5.650000 ( 5.667566)
>avg: 5.290000 0.000000 5.290000 ( 5.302962)
>total: 10.580000 0.000000 10.580000 ( 10.605924)
这看起来违反直觉,因为如果我猜的话,我会赌“-x”而不是“-1*x”更快。
为什么不同?还是我的测量存在重大缺陷?
如果您查看生成的字节码,可能会有所帮助。如果你把它简化为像
这样简单的东西puts RubyVM::InstructionSequence.compile(<<-CODE
a = 1
-1 * a
CODE
).to_a.join("\n")
puts RubyVM::InstructionSequence.compile(<<-CODE
a = 1
-a
CODE
).to_a.join("\n")
您还可以使用 #~$ ruby --dump insns -e 'code'
打印指令序列(如 @Stefan 所述),这实际上会输出更清晰的输出。
#~$ ruby --dump insns -e 'a = 1; -a'
== disasm: <RubyVM::InstructionSequence:<main>@-e>======================
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] a
0000 trace 1 ( 1)
0002 putobject 1
0004 setdynamic a, 0
0007 trace 1
0009 getdynamic a, 0
0012 send :-@, 0, nil, 0, <ic:0>
0018 leave
#~$ ruby --dump insns -e 'a = 1; -1 * a'
== disasm: <RubyVM::InstructionSequence:<main>@-e>======================
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] s1)
[ 2] a
0000 trace 1 ( 1)
0002 putobject 1
0004 setdynamic a, 0
0007 trace 1
0009 putobject -1
0011 getdynamic a, 0
0014 opt_mult <ic:1>
0016 leave
您会在字节码列表中注意到,在 -1 * a
Ruby 中执行了一个 opt_mult
,这基本上只是一个算术运算,并在内部进行了优化。
另一方面,-a
位是一个 opt_send_without_block
,这是一个实际的方法调用。我猜这就是第二个版本(有点)慢的原因。