1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

st.c: use power-of-two sizes to avoid slow modulo ops

* st.c (hash_pos): use bitwise AND to avoid slow modulo op
  (new_size): power-of-two sizes for hash_pos change
  (st_numhash): adjust for common keys due to lack of prime modulo
  [Feature #9425]
* hash.c (rb_any_hash): right shift for symbols
* benchmark/bm_hash_aref_miss.rb: added to show improvement
* benchmark/bm_hash_aref_sym_long.rb: ditto
* benchmark/bm_hash_aref_str.rb: ditto
* benchmark/bm_hash_aref_sym.rb: ditto
* benchmark/bm_hash_ident_num.rb: added to prevent regression
* benchmark/bm_hash_ident_obj.rb: ditto
* benchmark/bm_hash_ident_str.rb: ditto
* benchmark/bm_hash_ident_sym.rb: ditto

git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@45384 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
normal 2014-03-22 23:34:21 +00:00
parent 14c9cf885c
commit ef59670a44
12 changed files with 77 additions and 57 deletions

View file

@ -1,3 +1,19 @@
Sun Mar 23 08:12:27 2014 Eric Wong <e@80x24.org>
* st.c (hash_pos): use bitwise AND to avoid slow modulo op
(new_size): power-of-two sizes for hash_pos change
(st_numhash): adjust for common keys due to lack of prime modulo
[Feature #9425]
* hash.c (rb_any_hash): right shift for symbols
* benchmark/bm_hash_aref_miss.rb: added to show improvement
* benchmark/bm_hash_aref_sym_long.rb: ditto
* benchmark/bm_hash_aref_str.rb: ditto
* benchmark/bm_hash_aref_sym.rb: ditto
* benchmark/bm_hash_ident_num.rb: added to prevent regression
* benchmark/bm_hash_ident_obj.rb: ditto
* benchmark/bm_hash_ident_str.rb: ditto
* benchmark/bm_hash_ident_sym.rb: ditto
Sat Mar 22 22:56:45 2014 NARUSE, Yui <naruse@ruby-lang.org>
* addr2line.c (fill_lines): compare the file names of object in which

6
NEWS
View file

@ -84,3 +84,9 @@ with all sufficient information, see the ChangeLog file.
* struct RBignum is hidden. [Feature #6083]
Use rb_integer_pack and rb_integer_unpack instead.
* st hash table uses power-of-two sizes for speed [Feature #9425].
Lookups are 10-25% faster if using appropriate hash functions.
However, weaknesses in hash distribution can no longer be masked
by prime number-sized tables, so extensions may need to tweak
hash functions to ensure good distribution.

View file

@ -0,0 +1,5 @@
h = {}
strs = ('a'..'z').to_a.map!(&:freeze)
strs.each { |s| h[s] = s }
strs = ('A'..'Z').to_a
200_000.times { strs.each { |s| h[s] } }

View file

@ -0,0 +1,4 @@
h = {}
strs = ('a'..'z').to_a.map!(&:freeze)
strs.each { |s| h[s] = s }
200_000.times { strs.each { |s| h[s] } }

View file

@ -0,0 +1,4 @@
h = {}
syms = ('a'..'z').to_a.map(&:to_sym)
syms.each { |s| h[s] = s }
200_000.times { syms.each { |s| h[s] } }

View file

@ -0,0 +1,8 @@
h = {}
syms = %w[puts warn syswrite write stat bacon lettuce tomato
some symbols in this array may already be interned others should not be
hash browns make good breakfast but not cooked using prime numbers
shift for division entries delete_if keys exist?
].map!(&:to_sym)
syms.each { |s| h[s] = s }
200_000.times { syms.each { |s| h[s] } }

View file

@ -0,0 +1,4 @@
h = {}.compare_by_identity
nums = (1..26).to_a
nums.each { |n| h[n] = n }
200_000.times { nums.each { |n| h[n] } }

View file

@ -0,0 +1,4 @@
h = {}.compare_by_identity
objs = 26.times.map { Object.new }
objs.each { |o| h[o] = o }
200_000.times { objs.each { |o| h[o] } }

View file

@ -0,0 +1,4 @@
h = {}.compare_by_identity
strs = ('a'..'z').to_a
strs.each { |s| h[s] = s }
200_000.times { strs.each { |s| h[s] } }

View file

@ -0,0 +1,4 @@
h = {}.compare_by_identity
syms = ('a'..'z').to_a.map(&:to_sym)
syms.each { |s| h[s] = s }
200_000.times { syms.each { |s| h[s] } }

1
hash.c
View file

@ -132,6 +132,7 @@ rb_any_hash(VALUE a)
if (SPECIAL_CONST_P(a)) {
if (a == Qundef) return 0;
if (SYMBOL_P(a)) a >>= (RUBY_SPECIAL_SHIFT + 3); /* 3=ID_SCOPE_SHIFT */
hnum = rb_objid_hash((st_index_t)a);
}
else if (BUILTIN_TYPE(a) == T_STRING) {

74
st.c
View file

@ -33,8 +33,7 @@ typedef struct st_packed_entry {
#define STATIC_ASSERT(name, expr) typedef int static_assert_##name##_check[(expr) ? 1 : -1];
#define ST_DEFAULT_MAX_DENSITY 5
#define ST_DEFAULT_INIT_TABLE_SIZE 11
#define ST_DEFAULT_SECOND_TABLE_SIZE 19
#define ST_DEFAULT_INIT_TABLE_SIZE 16
#define ST_DEFAULT_PACKED_TABLE_SIZE 18
#define PACKED_UNIT (int)(sizeof(st_packed_entry) / sizeof(st_table_entry*))
#define MAX_PACKED_HASH (int)(ST_DEFAULT_PACKED_TABLE_SIZE * sizeof(st_table_entry*) / sizeof(st_packed_entry))
@ -85,7 +84,7 @@ static void rehash(st_table *);
#define EQUAL(table,x,y) ((x)==(y) || (*(table)->type->compare)((x),(y)) == 0)
#define do_hash(key,table) (st_index_t)(*(table)->type->hash)((key))
#define hash_pos(h,n) ((h) % (n))
#define hash_pos(h,n) ((h) & (n - 1))
#define do_hash_bin(key,table) hash_pos(do_hash((key), (table)), (table)->num_bins)
/* preparation for possible allocation improvements */
@ -140,69 +139,18 @@ remove_safe_packed_entry(st_table *table, st_index_t i, st_data_t never)
PHASH_SET(table, i, 0);
}
/*
* MINSIZE is the minimum size of a dictionary.
*/
#define MINSIZE 8
/*
Table of prime numbers 2^n+a, 2<=n<=30.
*/
static const unsigned int primes[] = {
ST_DEFAULT_INIT_TABLE_SIZE,
ST_DEFAULT_SECOND_TABLE_SIZE,
32 + 5,
64 + 3,
128 + 3,
256 + 27,
512 + 9,
1024 + 9,
2048 + 5,
4096 + 3,
8192 + 27,
16384 + 43,
32768 + 3,
65536 + 45,
131072 + 29,
262144 + 3,
524288 + 21,
1048576 + 7,
2097152 + 17,
4194304 + 15,
8388608 + 9,
16777216 + 43,
33554432 + 35,
67108864 + 15,
134217728 + 29,
268435456 + 3,
536870912 + 11,
1073741824 + 85,
0
};
static st_index_t
new_size(st_index_t size)
{
int i;
st_index_t i;
#if 0
for (i=3; i<31; i++) {
if ((1<<i) > size) return 1<<i;
if ((st_index_t)(1<<i) > size) return 1<<i;
}
return -1;
#else
st_index_t newsize;
for (i = 0, newsize = MINSIZE; i < numberof(primes); i++, newsize <<= 1) {
if (newsize > size) return primes[i];
}
/* Ran out of primes */
#ifndef NOT_RUBY
rb_raise(rb_eRuntimeError, "st_table too big");
#endif
return -1; /* should raise exception */
#endif
}
#ifdef HASH_LOG
@ -1685,5 +1633,17 @@ st_numcmp(st_data_t x, st_data_t y)
st_index_t
st_numhash(st_data_t n)
{
return (st_index_t)n;
/*
* This hash function is lightly-tuned for Ruby. Further tuning
* should be possible. Notes:
*
* - (n >> 3) alone is great for heap objects and OK for fixnum,
* however symbols perform poorly.
* - (n >> (RUBY_SPECIAL_SHIFT+3)) was added to make symbols hash well,
* n.b.: +3 to remove ID scope, +1 worked well initially, too
* - (n << 3) was finally added to avoid losing bits for fixnums
* - avoid expensive modulo instructions, it is currently only
* shifts and bitmask operations.
*/
return (st_index_t)((n>>(RUBY_SPECIAL_SHIFT+3)|(n<<3)) ^ (n>>3));
}