在对一些代码进行基准测试以找出时,如果在检查通过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中的符号集要慢得多。
我现在的问题是
更新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处于同一水平
我现在的结论
答案 0 :(得分:1)
这是ruby代码中的一个性能问题,修复得非常快: - )
问题:https://bugs.ruby-lang.org/issues/11396 提交:https://github.com/ruby/ruby/commit/442b77e72166c9c993e3c6663568431123510dec