Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why map {}.compact is faster than each_with_object([])?

Tags:

ruby

I did some benchmarks:

require 'benchmark'

words = File.open('/usr/share/dict/words', 'r') do |file|
  file.each_line.take(1_000_000).map(&:chomp)
end

Benchmark.bmbm(20) do |x|
  GC.start
  x.report(:map) do
    words.map do |word|
      word.size if word.size > 5
    end.compact
  end

  GC.start
  x.report(:each_with_object) do
    words.each_with_object([]) do |word, long_sizes|
      long_sizes << word.size if word.size > 5
    end
  end
end

Output (ruby 2.3.0):

Rehearsal --------------------------------------------------------
map                    0.020000   0.000000   0.020000 (  0.016906)
each_with_object       0.020000   0.000000   0.020000 (  0.024695)
----------------------------------------------- total: 0.040000sec

                           user     system      total        real
map                    0.010000   0.000000   0.010000 (  0.015004)
each_with_object       0.020000   0.000000   0.020000 (  0.024183)

I cannot understand it because I thought that each_with_object should be faster: it needs only 1 loop and 1 new object to create a new array instead of 2 loops and 2 new objects in case when we combine map and compact. Any ideas?

like image 210
Ilya Avatar asked Dec 18 '22 06:12

Ilya


1 Answers

Array#<< needs to reallocate memory if the original memory space doesn't have enough room to hold the new item. See the implementation, especially this line

VALUE target_ary = ary_ensure_room_for_push(ary, 1);

While Array#map doesn't have to reallocate memory from time to time because it already knows the size of the result array. See the implementation, especially

collect = rb_ary_new2(RARRAY_LEN(ary));

which allocates the same size of memory as the original array.

like image 60
Aetherus Avatar answered Jan 07 '23 13:01

Aetherus