Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is `-1 * x` faster than `-x` and why?

Using this code:

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

I get this result back on 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)

That looks counter intuitive, because if I would guess I would bet on "-x" not "-1*x" to be faster.

Why the difference? Or do I have some major flaw in my measurement?

like image 830
Szymon Jeż Avatar asked Dec 11 '15 12:12

Szymon Jeż


1 Answers

It might help if you look at the generated bytecode. If you reduce it to something simple like

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")

You can also use #~$ ruby --dump insns -e 'code' to print the instruction sequence (as mentioned by @Stefan) which will actually output a much cleaner output.

#~$ 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

you'll notice in the bytecode listing that in the -1 * a Ruby does an opt_mult which is basically just an arithmetic operation, and optimized internally. On the other hand, the -a bit is an opt_send_without_block which is an actual method call. My guess is this is why the second version is (a bit) slower.

like image 102
eugen Avatar answered Oct 25 '22 05:10

eugen