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

标签 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

不出所料,set 比 array 快,即使没有我预期的那么快。让我感到奇怪的是,包含字符串的集合比包含符号的集合快,而对于数组,符号集合要快得多。我多次重复脚本,比例保持不变。

为了获得更多见解,我使用 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 的数字作为字符串/符号键创建了一个散列,并使用示例进行了 Hash[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:

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

我现在的结论

  • 始终对您的实际代码进行基准测试,并考虑差异是否真的可见。在我的用例中,即使每个 Web 请求进行了大约 50 次这种检查,总体时间差异也只有几分之一毫秒
  • ruby 2.3 和希望即将到来的 2.2.3 中的反向移植代码似乎再次通过哈希符号变得更快
  • 不要做出“符号总是更快”或“Set.include?总是比 Array.include 快?”
  • Symbol Garbage Collection 带有一个很大的 performance penalty ,如果你处理非常大的哈希。

最佳答案

这是 ruby​​ 代码中的一个性能问题,很快就修复了:-)

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

关于ruby - ruby Set.include 中的性能异常?带有符号(2.2.2 与 2.1.6),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31631284/

相关文章:

performance - Scala 模式匹配性能

ruby - 包括鞋中鞋套餐

ruby - 组合的递归函数

ruby-on-rails - 使用文件名创建目录哈希

performance - Haskell/GHC 内存了多少?

代码基准测试统计 -

ruby-on-rails - 将配置存储在 Rails 之类的文件中

ruby-on-rails - 无法构建 gem native 扩展 (mkmf (LoadError)) - Ubuntu 12.04

c# - 在 C# 中使用命名空间会影响性能吗?

python - 有效地从列表中删除重复项