ruby Set.include中的性能异常?带符号(2.2.2 vs 2.1.6)

时间:2015-07-25 21:24:37

标签: ruby performance

在对一些代码进行基准测试以找出时,如果在检查通过include包含的元素时使用set真的比array快吗?我在集合中发现了一些关于字符串和符号的性能异常。

首先我用于基准测试的脚本。它基本上创建一个包含50个随机50个字符串的数组,获取20个样本并检查是否包含所有样本值。相同的数据用于创建一组字符串,一组符号和一组符号。

require 'benchmark/ips'
require 'Set'

collection_size = 50
element_length = 50
sample_size = 20

Benchmark.ips do |x|

  array_of_strings = begin
    (1..collection_size).map {|pos| (0..element_length).map { ('a'..'z').to_a[rand(26)] }.join }
  end
  array_of_symbols = array_of_strings.map(&:to_sym)
  set_of_strings = Set.new(array_of_strings)
  set_of_symbols = Set.new(array_of_symbols)

  sample_of_strings = array_of_strings.sample(sample_size)
  sample_of_symbols = array_of_symbols.sample(sample_size)

  x.report("array_of_strings: #{collection_size} elements with length #{element_length}, sample size #{sample_of_strings.length}") {
    sample_of_strings.each do |s|
      array_of_strings.include? s
    end
  }

  x.report("set_of_strings: #{collection_size} elements with length #{element_length}, sample size #{sample_of_strings.length}") {
    sample_of_strings.each do |s|
      set_of_strings.include? s
    end
  }

  x.report("array_of_symbols: #{collection_size} elements with length #{element_length}, sample size #{sample_of_symbols.length}") {
    sample_of_symbols.each do |s|
      array_of_symbols.include? s
    end
  }

  x.report("set_of_symbols: #{collection_size} elements with length #{element_length}, sample size #{sample_of_symbols.length}") {
    sample_of_symbols.each do |s|
      set_of_symbols.include? s
    end
  }

  x.compare!  
end

测试系统是2011 Macbook Pro,运行OSX 10.10.4,两个ruby版本都是使用rvm 1.26.11安装的。

使用ruby 2.2.2执行此操作时,我得到以下结果:

set_of_strings:      145878.6 i/s
set_of_symbols:      100100.1 i/s - 1.46x slower
array_of_symbols:    81680.0 i/s - 1.79x slower
array_of_strings:    43545.9 i/s - 3.35x slower

正如预期的那样,该集合比数组快,即使没有我预期的那么快。让我感到奇怪的是,包含字符串的集合比包含符号的集合更快,而对于数组,符号1更快。我多次重复剧本,比例保持不变。

为了获得更多的见解,我使用ruby 2.1.6运行了基准脚本并得到了这些结果:

set_of_symbols:      202362.3 i/s
set_of_strings:      145844.1 i/s - 1.39x slower
array_of_symbols:    39158.1 i/s - 5.17x slower
array_of_strings:    24687.8 i/s - 8.20x slower

再次设置比数组更快,但是数组性能比ruby 2.2.2差得多,似乎性能改善与2.1.6相比非常好。

奇怪的是集合的结果。包含字符串的集合在ruby 2.2.2和2.1.6中每秒达到大约相同的指令,这应该是它应该如何。但是在ruby 2.1.6中,包含符号的集合快两倍。而不是2.2.2!

我还改变了基准脚本的参数,结果保持不变。字符串集在2.1.6和2.2.2中达到大约相同的i / s,而2.2.2中的符号集要慢得多。

我现在的问题是

  • 任何人都可以解释这种行为吗?
  • 我的基准测试脚本中有什么我忽略的吗?
  • 如果其他人可以重现这一点,应该如何向ruby开发者报告?

更新1:

似乎潜在的问题是Hash类本身,它用于在Set类中存储值。刚刚创建了一个数字为1到1000的哈希作为字符串/符号键,并使用样本进行哈希[k]访问。

Ruby 2.2.2:

h_string: 1000 keys, sample size 200:    29374.4 i/s
h_symbol: 1000 keys, sample size 200:    10604.7 i/s - 2.77x slower

Ruby 2.1.6。:

h_symbol: 1000 keys, sample size 200:    31561.9 i/s
h_string: 1000 keys, sample size 200:    25589.7 i/s - 1.23x slower

出于某种原因,在2.2.2中使用符号作为哈希键要慢得多,使用的基准测试脚本:

require 'benchmark/ips'

collection_size = 1000
sample_size = 200

Benchmark.ips do |x|

  h_string = Hash.new 
  h_symbol = Hash.new

  (1..collection_size).each {|k| h_string[k.to_s] = 1}
  (1..collection_size).each {|k| h_symbol[k.to_s.to_sym] = 1}

  sample_of_string_keys = h_string.keys.sample(sample_size)
  sample_of_symbol_keys = sample_of_string_keys.map(&:to_sym)

  x.report("h_string: #{collection_size} keys, sample size #{sample_of_string_keys.length}") {
    sample_of_string_keys.each do |s|
      h_string[s]
    end
  }

  x.report("h_symbol: #{collection_size} keys, sample size #{sample_of_symbol_keys.length}") {
    sample_of_symbol_keys.each do |s|
      h_symbol[s]
    end
  }

  x.compare!  
end

更新2:

我用最新的ruby 2.3.0dev (2015-07-26 trunk 51391) [x86_64-darwin14]重复了这些测试,那里带有符号的集合再次快得多,而且对于一个级别的小型collection_size和sample_size,使用ruby 2.1.6

对于更大的数字,例如具有10000个值的哈希并检查100个样本,ruby 2.1.6达到了ruby-head迭代的大约两倍(并且几乎是ruby 2.2.2的3倍)。所以看来ruby-head中正在进行一些改进,但仍然不足以恢复旧的性能。

更新3:

感谢@cremno的comment我用2.2分支重新检查了我的初始设定基准,2.2与2.1.6处于同一水平

我现在的结论

  • 始终对您的实际代码进行基准测试,并考虑差异是否真的可见。在我的使用案例中,即使对每个Web请求进行大约50次检查,总时间差仅为几分之一毫秒
  • ruby​​ 2.3和backported代码希望即将推出的2.2.3似乎再次使用Hashes中的符号变得更快
  • 不要做出像“符号总是更快”这样的假设。或者' Set.include?总是比Array.include快?'
  • 符号垃圾收集带有一个很大的performance penalty,如果你处理的是大哈希。

1 个答案:

答案 0 :(得分:1)

这是ruby代码中的一个性能问题,修复得非常快: - )

问题:https://bugs.ruby-lang.org/issues/11396 提交:https://github.com/ruby/ruby/commit/442b77e72166c9c993e3c6663568431123510dec