为什么 map {}.compact 比 each_with_object([]) 更快?

4

我进行了一些基准测试:

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

输出(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)

我不太理解它,因为我认为each_with_object应该更快:它只需要1个循环和1个新对象来创建一个新数组,而不是在组合mapcompact时需要2个循环和2个新对象。

有什么想法吗?


因为 long_words << word 会不时地导致内存分配。 - Pavel Mikhailyuk
这是一个时间和内存的权衡:map / compact 更快但使用更多内存,而 each_with_object 则较慢但消耗较少内存。 - Stefan
2
请注意,words.select { |word| word.size > 5 } 比你提供的两个示例更短、更合乎逻辑和更快。 - Eric Duminil
@EricDuminil,当然,这是显而易见的。实例只是为了基准测试。我编辑了问题,所以你的评论现在不相关了。 - Ilya
你赢了:words.map{|w| w.size }.select{|s| s > 5} 比起你的方法慢 :D - Eric Duminil
1个回答

11

Array#<< 如果原内存空间不足以容纳新的元素,需要重新分配内存。请查看实现,特别是这一行。

VALUE target_ary = ary_ensure_room_for_push(ary, 1);

虽然Array#map不必时不时地重新分配内存,因为它已经知道结果数组的大小。请参阅实现,特别是

collect = rb_ary_new2(RARRAY_LEN(ary));

它分配与原始数组相同大小的内存。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接