Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return nil from assignment benchmarks #50

Conversation

@charliesome
Copy link
Contributor

commented Aug 2, 2015

The parallel assignment benchmark only allocates an array because the assignment expression is the last expression in the method (and so its result is returned to the caller). If Ruby detects that an expression's result is unused (as would be the case with most assignment expressions), it will avoid allocating the array and just assign directly.

Inspecting the bytecode generated for parallel assignment in both cases shows off this optimisation:

λ ruby --dump=insns -e 'a, b, c = 1, 2, 3'
== disasm: <RubyVM::InstructionSequence:<main>@-e>======================
local table (size: 4, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@5] s1)
[ 4] a          [ 3] b          [ 2] c
0000 trace            1                                               (   1)
0002 duparray         [1, 2, 3]
0004 dup
0005 expandarray      3, 0
0008 setlocal_OP__WC__0 4
0010 setlocal_OP__WC__0 3
0012 setlocal_OP__WC__0 2
0014 leave
λ ruby --dump=insns -e 'a, b, c = 1, 2, 3; nil'
== disasm: <RubyVM::InstructionSequence:<main>@-e>======================
local table (size: 4, argc: 0 [opts: 0, rest: -1, post: 0, block: -1, keyword: 0@5] s1)
[ 4] a          [ 3] b          [ 2] c
0000 trace            1                                               (   1)
0002 putobject_OP_INT2FIX_O_1_C_
0003 putobject        2
0005 putobject        3
0007 setlocal_OP__WC__0 2
0009 setlocal_OP__WC__0 3
0011 setlocal_OP__WC__0 4
0013 trace            1
0015 putnil
0016 leave

In the first bytecode dump, Ruby has no option but to allocate the array because the assignment expression is the last expression. Even still, we can see that the Ruby VM is still clever enough to see that each element in the array has no evaluation side effects and so it is able to just dup an pre-allocated array rather than building a new one up from scratch.

In the second bytecode dump, because the assignment is not the last expression and does not need to return a value, the Ruby VM takes a shortcut and just assigns the variables directly rather than creating an array. 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.

Here's the benchmark results before changing the benchmarked methods to return nil:

Calculating -------------------------------------
 Parallel Assignment    81.224k i/100ms
Sequential Assignment
                       101.963k i/100ms
-------------------------------------------------
 Parallel Assignment      2.610M (± 3.2%) i/s -     13.077M
Sequential Assignment
                          6.113M (± 3.7%) i/s -     30.589M

Comparison:
Sequential Assignment:  6113471.7 i/s
 Parallel Assignment:  2609542.4 i/s - 2.34x slower

The benchmark results after changing the benchmarked methods to return nil show a clear improvement in favour of parallel assignment:

Calculating -------------------------------------
 Parallel Assignment   103.520k i/100ms
Sequential Assignment
                       105.474k i/100ms
-------------------------------------------------
 Parallel Assignment      7.049M (± 2.7%) i/s -     35.300M
Sequential Assignment
                          6.159M (± 2.3%) i/s -     30.798M

Comparison:
 Parallel Assignment:  7048523.9 i/s
Sequential Assignment:  6159203.7 i/s - 1.14x slower

JuanitoFatas added a commit that referenced this pull request Aug 6, 2015

Merge pull request #50 from charliesome/fix-parallel-assignment-bench…
…mark

Return nil from assignment benchmarks

@JuanitoFatas JuanitoFatas merged commit 2f08296 into JuanitoFatas:master Aug 6, 2015

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
@JuanitoFatas

This comment has been minimized.

Copy link
Owner

commented Aug 6, 2015

@charliesome Thank you for your detailed explanations. 🙇 I was so wrong 😓

@etiennebarrie

This comment has been minimized.

Copy link

commented Aug 6, 2015

So now the fast one is called slow and vice versa, right?

@charliesome

This comment has been minimized.

Copy link
Contributor Author

commented Aug 6, 2015

@etiennebarrie Oh heh, yeah I forgot to update that

@JuanitoFatas

This comment has been minimized.

Copy link
Owner

commented Aug 7, 2015

@etiennebarrie Fixed in 53894bf, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.