【问题标题】:Why is array.index faster than array.include?为什么 array.index 比 array.include 快?
【发布时间】:2014-11-04 22:20:08
【问题描述】:

我正在处理一些大型数据集,并试图提高性能。我需要确定一个对象是否包含在数组中。我正在考虑使用indexinclude?,所以我对两者都进行了基准测试。

require 'benchmark'

a = (1..1_000_000).to_a
num = 100_000
reps = 100

Benchmark.bmbm do |bm|
  bm.report('include?') do
    reps.times { a.include? num }
  end
  bm.report('index') do
    reps.times { a.index num }
  end
end

令人惊讶的是(对我来说)index 的速度要快得多。

               user     system      total        real
include?   0.330000   0.000000   0.330000 (  0.334328)
index      0.040000   0.000000   0.040000 (  0.039812)

由于index 提供的信息比include? 多,我预计它会稍微慢一些,尽管情况并非如此。为什么更快?

(我知道index直接来自于数组类,而include?是继承自Enumerable。这能解释一下吗?)

【问题讨论】:

标签: ruby arrays performance


【解决方案1】:

查看 Ruby MRI 源代码,似乎index 使用了优化的rb_equal_opt,而include? 使用了rb_equal。这可以在rb_ary_includesrb_ary_index 中看到。 Here 是进行更改的提交。我并不清楚为什么它用于index 而不是include?

您可能还会发现阅读此feature 的讨论会很有趣

【讨论】:

    【解决方案2】:

    我为基准测试运行了相同的测试。好像包括?比索引快,虽然不是很一致。 这是我针对两种不同场景的结果。

    1. ruby 代码和你的一样
                   user     system      total        real
    index      0.065803   0.000652   0.066455 (  0.067181)
    include?   0.065551   0.000590   0.066141 (  0.066894)
    
    1. 另一个基准测试
                       user     system      total        real
        index      0.000034   0.000005   0.000039 (  0.000037)
        include?   0.000017   0.000001   0.000018 (  0.000017)
    

    代码:

    require 'benchmark'
    
    # parse ranks and return number of reports to using index
    def solution_using_index(ranks)
      return 0 if ranks.nil? || ranks.empty? || ranks.length <= 1
      return ((ranks[0] - ranks[1] == 1) || (ranks[1] - ranks[0] == 1) ?  1 : 0) if ranks.length == 2
      return 0 if ranks.max > 1000000000 || ranks.min < 0
    
      grouped_ranks = ranks.group_by(&:itself)
      report_to, rank_keys= 0, grouped_ranks.keys
      rank_keys.each {|rank| report_to += grouped_ranks[rank].length if rank_keys.index(rank+1) }
      report_to
    end
    
    # parse ranks and return number of reports to using include
    def solution_using_include(ranks)
      return 0 if ranks.nil? || ranks.empty? || ranks.length <= 1
      return ((ranks[0] - ranks[1] == 1) || (ranks[1] - ranks[0] == 1) ?  1 : 0) if ranks.length == 2
      return 0 if ranks.max > 1000000000 || ranks.min < 0
    
      grouped_ranks = ranks.group_by(&:itself)
      report_to, rank_keys= 0, grouped_ranks.keys
      rank_keys.each {|rank| report_to += grouped_ranks[rank].length if rank_keys.include?(rank+1) }
      report_to
    end
    
    test_data = [[3, 4, 3, 0, 2, 2, 3, 0, 0], [4, 4, 3, 3, 1, 0], [4, 2, 0] ]
    
    Benchmark.bmbm do |bm|
      bm.report('index') do
        test_data.each do |ranks|
          reports_to = solution_using_index(ranks)
        end
      end
      bm.report('include?') do
        test_data.each do |ranks|
          reports_to = solution_using_include(ranks)
        end
      end
    end
    
    

    【讨论】:

    • 您的结果对我来说似乎与内部一致? include? 总是更快。但是,你读过cmets吗?该错误已修复,因此您的结果。
    • 是的,这对我来说是一种误导,所以想证明它的合理性。我用 2.6.0, 2.6.0preview1, 2.6.0preview2, 2.6.0preview3 和它的一致测试它。
    【解决方案3】:

    如果性能是您的目标,您应该使用 Array#bsearch,它使用二进制搜索遍历数组。

    https://ruby-doc.org/core-2.7.0/Array.html#method-i-bsearch

    a.bsearch {|a| num <=> a }
    

    它同时抽indexinclude

    Rehearsal --------------------------------------------
    include?   0.108172   0.000805   0.108977 (  0.112928)
    index      0.122730   0.000502   0.123232 (  0.126323)
    bsearch    0.000254   0.000027   0.000281 (  0.000354)
    ----------------------------------- total: 0.232490sec
    
                   user     system      total        real
    include?   0.106727   0.000036   0.106763 (  0.108495)
    index      0.107732   0.000330   0.108062 (  0.110272)
    bsearch    0.000201   0.000008   0.000209 (  0.000206)
    

    【讨论】:

      猜你喜欢
      • 2019-11-02
      • 2015-07-24
      • 1970-01-01
      • 2019-04-18
      • 2014-07-06
      • 2011-07-08
      相关资源
      最近更新 更多