2015-12-11 25 views
8

utilizzando questo codice:Perché `-1 * x` più veloce di` -x` e perché?

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 

ottengo questo risultato di nuovo in 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) 

che sembra contro intuitivo, perché se io immagino ci avrei scommesso sulla "x" non "-1 * x "per essere più veloce.

Perché la differenza? O ho qualche difetto importante nella mia misurazione?

+0

Questo codice è riproducibile sulle nostre macchine? 'CAPTION' e'FORMAT 'vengono contenti quali valori? – Elyasin

risposta

12

Potrebbe essere d'aiuto se si guarda il codice byte generato. Se si riduce a qualcosa di semplice come

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

È inoltre possibile utilizzare #~$ ruby --dump insns -e 'code' per stampare la sequenza di istruzioni (come detto da @Stefan), che sarà effettivamente in uscita una potenza molto più pulito.

#~$ 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    :[email protected], 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 

si noterà nella lista che nel -1 * a Rubino bytecode fa un opt_mult che è fondamentalmente solo un'operazione aritmetica, e ottimizzato internamente. D'altra parte, il -a bit è un opt_send_without_block che è una chiamata di metodo effettiva. La mia ipotesi è che questo è il motivo per cui la seconda versione è (un po ') più lenta.

+1

solo per curiosità: come sei venuto a conoscenza dell'esistenza della classe 'RubyVM :: InstructionSequence'? Voglio dire, cosa ti ha portato a scoprirlo. –

+0

Non riesco a ricordare dove l'ho sentito per la prima volta, ma se ti interessa questo tipo di dettagli di basso livello, penso che Ruby Under a Microscope di Pat Shaughnessy sia un ottimo libro. – eugen

+4

Nota che puoi anche eseguire 'ruby --dump insns -e 'a = 1; -a'' per stampare la sequenza di istruzioni. – Stefan

Problemi correlati