Ruby 中的哈希 "has_key" 复杂度
Hash "has_key" complexity in Ruby
我有一个散列 vars = {"a" => "Name", "b" => "Address" , "c" => "Phone"}
。我想检查这条线路的性能:
vars.has_key(:b)?
是 O(1) 还是 O(散列大小)?
该方法的预期复杂度是恒定的。
简单基准测试:
require 'benchmark'
iterations = 10_000
small = 10
big = 1_000_000
small_hash = {}
big_hash = {}
(1..small).each do |i|
small_hash[i] = i
end
(1..big).each do |i|
big_hash[i] = i
end
Benchmark.bmbm do |bm|
bm.report('Small Hash') do
iterations.times { small_hash.has_key?(1) }
end
bm.report('Big Hash') do
iterations.times { big_hash.has_key?(1) }
end
end
运行 测试:
$ ruby has_key_test.rb
user system total real
Small Hash 0.000000 0.000000 0.000000 ( 0.001167)
Big Hash 0.000000 0.000000 0.000000 ( 0.001171)
所以是的,我认为我们可以考虑成本常数 O(1)(至少,不检查内部 MRI 实现)。
def fake_h(n)
n.times.inject({}){|o,x| o[x] = :a; o}
end
n = 1000000;
h1 = fake_h(1);
h10 = fake_h(10);
h100 = fake_h(100);
h1000 = fake_h(1000);
h10000 = fake_h(10000);
h100000 = fake_h(100000);
h1000000 = fake_h(1000000);
Benchmark.bm do |x|
x.report { n.times{|t| h1.has_key?(t) } }
x.report { n.times{|t| h10.has_key?(t) } }
x.report { n.times{|t| h100.has_key?(t) } }
x.report { n.times{|t| h1000.has_key?(t) } }
x.report { n.times{|t| h10000.has_key?(t) } }
x.report { n.times{|t| h100000.has_key?(t) } }
x.report { n.times{|t| h1000000.has_key?(t) } }
end
# Result :
user system total real
0.200000 0.000000 0.200000 ( 0.204647)
0.210000 0.000000 0.210000 ( 0.205677)
0.210000 0.000000 0.210000 ( 0.214393)
0.210000 0.000000 0.210000 ( 0.206382)
0.210000 0.000000 0.210000 ( 0.208998)
0.200000 0.000000 0.200000 ( 0.206821)
0.220000 0.000000 0.220000 ( 0.213316)
具有 1 个条目或 100 万个条目的散列之间的区别是……最小的。
has_key
的源码是(http://ruby-doc.org/core-1.9.3/Hash.html#method-i-has_key-3F)
rb_hash_has_key(VALUE hash, VALUE key)
{
if (!RHASH(hash)->ntbl)
return Qfalse;
if (st_lookup(RHASH(hash)->ntbl, key, 0)) {
return Qtrue;
}
return Qfalse;
}
st_lookup
有以下片段 (https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/st.c#L383):
if (table->entries_packed) {
st_index_t i = find_packed_index(table, hash_val, key);
if (i < table->real_entries) {
if (value != 0) *value = PVAL(table, i);
return 1;
}
return 0;
}
这告诉我们如果 entries_packed
那么 ruby 使用索引 (O(1)) 否则它使用非索引搜索 (O(n))。
entries_packed
的值似乎取决于散列的大小:(https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/st.c#L41)
#define MAX_PACKED_HASH (int)(ST_DEFAULT_PACKED_TABLE_SIZE * sizeof(st_table_entry*) / sizeof(st_packed_entry))
https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/st.c#L219
tbl->entries_packed = size <= MAX_PACKED_HASH;
size
是索引的一种大小。
您可以在 ruby 来源中找到更多详细信息,但它的复杂性并不总是 O(1),而是取决于散列的大小。 (关于其索引的大小)
我有一个散列 vars = {"a" => "Name", "b" => "Address" , "c" => "Phone"}
。我想检查这条线路的性能:
vars.has_key(:b)?
是 O(1) 还是 O(散列大小)?
该方法的预期复杂度是恒定的。
简单基准测试:
require 'benchmark'
iterations = 10_000
small = 10
big = 1_000_000
small_hash = {}
big_hash = {}
(1..small).each do |i|
small_hash[i] = i
end
(1..big).each do |i|
big_hash[i] = i
end
Benchmark.bmbm do |bm|
bm.report('Small Hash') do
iterations.times { small_hash.has_key?(1) }
end
bm.report('Big Hash') do
iterations.times { big_hash.has_key?(1) }
end
end
运行 测试:
$ ruby has_key_test.rb
user system total real
Small Hash 0.000000 0.000000 0.000000 ( 0.001167)
Big Hash 0.000000 0.000000 0.000000 ( 0.001171)
所以是的,我认为我们可以考虑成本常数 O(1)(至少,不检查内部 MRI 实现)。
def fake_h(n)
n.times.inject({}){|o,x| o[x] = :a; o}
end
n = 1000000;
h1 = fake_h(1);
h10 = fake_h(10);
h100 = fake_h(100);
h1000 = fake_h(1000);
h10000 = fake_h(10000);
h100000 = fake_h(100000);
h1000000 = fake_h(1000000);
Benchmark.bm do |x|
x.report { n.times{|t| h1.has_key?(t) } }
x.report { n.times{|t| h10.has_key?(t) } }
x.report { n.times{|t| h100.has_key?(t) } }
x.report { n.times{|t| h1000.has_key?(t) } }
x.report { n.times{|t| h10000.has_key?(t) } }
x.report { n.times{|t| h100000.has_key?(t) } }
x.report { n.times{|t| h1000000.has_key?(t) } }
end
# Result :
user system total real
0.200000 0.000000 0.200000 ( 0.204647)
0.210000 0.000000 0.210000 ( 0.205677)
0.210000 0.000000 0.210000 ( 0.214393)
0.210000 0.000000 0.210000 ( 0.206382)
0.210000 0.000000 0.210000 ( 0.208998)
0.200000 0.000000 0.200000 ( 0.206821)
0.220000 0.000000 0.220000 ( 0.213316)
具有 1 个条目或 100 万个条目的散列之间的区别是……最小的。
has_key
的源码是(http://ruby-doc.org/core-1.9.3/Hash.html#method-i-has_key-3F)
rb_hash_has_key(VALUE hash, VALUE key)
{
if (!RHASH(hash)->ntbl)
return Qfalse;
if (st_lookup(RHASH(hash)->ntbl, key, 0)) {
return Qtrue;
}
return Qfalse;
}
st_lookup
有以下片段 (https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/st.c#L383):
if (table->entries_packed) {
st_index_t i = find_packed_index(table, hash_val, key);
if (i < table->real_entries) {
if (value != 0) *value = PVAL(table, i);
return 1;
}
return 0;
}
这告诉我们如果 entries_packed
那么 ruby 使用索引 (O(1)) 否则它使用非索引搜索 (O(n))。
entries_packed
的值似乎取决于散列的大小:(https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/st.c#L41)
#define MAX_PACKED_HASH (int)(ST_DEFAULT_PACKED_TABLE_SIZE * sizeof(st_table_entry*) / sizeof(st_packed_entry))
https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/st.c#L219
tbl->entries_packed = size <= MAX_PACKED_HASH;
size
是索引的一种大小。
您可以在 ruby 来源中找到更多详细信息,但它的复杂性并不总是 O(1),而是取决于散列的大小。 (关于其索引的大小)