-
Notifications
You must be signed in to change notification settings - Fork 379
Description
This one is probably the most confusing benchmark I've ever seen. Original idea was that sequential assignment is faster than parallel (https://speakerdeck.com/sferik/writing-fast-ruby?slide=45). Later @charliesome found (#50) that this benchmark was incorrect because Ruby creates new array with all variables when parallel assignment is last expression in a method. After recalculation parallel assignment became faster than sequential.
Which one is actually faster?
I claim that none of them. Parallel assignment is actually equal to sequential. In the first comment we can read: "In fact, this is even faster than splitting these assignments out over multiple lines because the compiler does not need to emit a per-line trace instruction for each assignment."
I wonder why nobody broaden this topic. "trace" instruction is emitted for every line of your ruby program. Not only for assignments, but for every line. Does it mean we should write our programs as a oneliners? Absolutely not! "trace" has very low overhead. But suppose you are paranoid... Surprise! Ruby VM (MRI) allows you to disable emitting this instructions completely. Here is the trick:
RubyVM::InstructionSequence.compile_option = {
trace_instruction: false
}
Put this one in a file "disable_trace.rb" and load it before loading your program (you can't load this in the same script because it's too late, your program is already compiled to bytecode).
$ ruby -r./disable_trace assignment.rb
Calculating -------------------------------------
Parallel Assignment 149.276k i/100ms
Sequential Assignment
149.712k i/100ms
-------------------------------------------------
Parallel Assignment 7.963M (± 5.2%) i/s - 39.707M
Sequential Assignment
7.947M (± 5.2%) i/s - 39.674M
Comparison:
Parallel Assignment: 7962929.1 i/s
Sequential Assignment: 7946966.7 i/s - 1.00x slower
This is how your bytecode actually looks like when you disable emitting "trace" instruction:
puts RubyVM::InstructionSequence.compile("a = 1;\nb = 2;", nil, nil, nil, trace_instruction: false).disasm
== disasm: #<ISeq:<compiled>@<compiled>>================================
local table (size: 3, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, kw: -1@-1, kwrest: -1])
[ 3] a [ 2] b
0000 putobject_OP_INT2FIX_O_1_C_ ( 1)
0001 setlocal_OP__WC__0 3
0003 putobject 2 ( 2)
0005 dup
0006 setlocal_OP__WC__0 2
0008 leave
See? No trace instructions.
Summary:
- Sequential is not slower or faster than parallel, they are equal.
- Ruby adds "trace" instruction for every line of code, not only for assignments.
- Usually when you use multiple assignments you do it in multiple lines. This is why you might think that sequential assignment is slower than parallel. But remember: "Correlation does not imply causation".
- You can disable emitting "trace" instruction if you are paranoid.
- In practice it won't make too much difference if your program does something more seriously.
Also, small digression. It would be better to educate people WHY and WHEN something can be slower/faster rather than making such a strong statements like "X is faster than Y".