Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Range literals are slower than Range.new #2214

Closed
tsion opened this Issue · 1 comment

2 participants

@tsion
Collaborator

I noticed the other day that 1..10 range literals compile to less efficient code than Range.new(1, 10) does. This is because range literals compile to a direct Range.new call without any optimization while Range.new is recognized as a new call and compiled with the SendFastNew optimization.

Here's a comparison of the generated bytecode:

# For 1..10:
0000:  push_cpath_top             
0001:  find_const                 0
0003:  meta_push_1                
0004:  push_int                   10
0006:  send_stack                 :new, 2
0009:  ret  

# For Range.new(1, 10)
0000:  push_const_fast            :Range, 1
0003:  dup_top                    
0004:  check_serial               :new, 47
0007:  goto_if_false              24
0009:  allow_private              
0010:  send_stack                 :allocate, 0
0013:  dup_top                    
0014:  meta_push_1                
0015:  push_int                   10
0017:  allow_private              
0018:  send_stack                 :initialize, 2
0021:  pop                        
0022:  goto                       30
0024:  meta_push_1                
0025:  push_int                   10
0027:  send_stack                 :new, 2
0030:  ret     

I used this benchmark:

require 'benchmark'

n = 10_000_000

Benchmark.bmbm do |x|
  x.report("1..10") { n.times { 1..10 } }
  x.report("Range.new(1, 10)") { n.times { Range.new(1, 10) } }
end

And got these results:

Rehearsal ----------------------------------------------------
1..10              3.923333   0.016666   3.939999 (  3.955068)
Range.new(1, 10)   1.083333   0.003334   1.086667 (  1.092013)
------------------------------------------- total: 5.026666sec

                       user     system      total        real
1..10              3.419999   0.010000   3.429999 (  3.440985)
Range.new(1, 10)   1.036667   0.006666   1.043333 (  1.046448)

I'm using this version: rubinius 2.0.0.rc1 (1.9.3 508ad758 yyyy-mm-dd JI) [x86_64-unknown-linux-gnu]

@jc00ke
Owner

Some ips results

require 'benchmark/ips'

Benchmark.ips do |x|
  x.report("literal") { 1..100 }
  x.report("method send") { Range.new(1, 100) }
end
Calculating -------------------------------------
             literal    105063 i/100ms
         method send    225110 i/100ms
-------------------------------------------------
             literal  2139681.9 (±5.9%) i/s -   10716426 in   5.027467s
         method send  4127918.5 (±5.3%) i/s -   20710120 in   5.032144s
@dbussink dbussink closed this issue from a commit
@dbussink dbussink Use allocate + initialize path for Range.new
MRI also doesn't call Range.new, it doesn't even call Range#initialize.
So this change doesn't break anything in that sense and would still
allow a custom initialize if people would want to hack stuff.

Fixes #2214
e986a83
@dbussink dbussink closed this in e986a83
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.