Skip to content

Commit ef59670

Browse files
author
normal
committed
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 ruby#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
1 parent 14c9cf8 commit ef59670

12 files changed

+77
-57
lines changed

ChangeLog

+16
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,19 @@
1+
Sun Mar 23 08:12:27 2014 Eric Wong <[email protected]>
2+
3+
* st.c (hash_pos): use bitwise AND to avoid slow modulo op
4+
(new_size): power-of-two sizes for hash_pos change
5+
(st_numhash): adjust for common keys due to lack of prime modulo
6+
[Feature #9425]
7+
* hash.c (rb_any_hash): right shift for symbols
8+
* benchmark/bm_hash_aref_miss.rb: added to show improvement
9+
* benchmark/bm_hash_aref_sym_long.rb: ditto
10+
* benchmark/bm_hash_aref_str.rb: ditto
11+
* benchmark/bm_hash_aref_sym.rb: ditto
12+
* benchmark/bm_hash_ident_num.rb: added to prevent regression
13+
* benchmark/bm_hash_ident_obj.rb: ditto
14+
* benchmark/bm_hash_ident_str.rb: ditto
15+
* benchmark/bm_hash_ident_sym.rb: ditto
16+
117
Sat Mar 22 22:56:45 2014 NARUSE, Yui <[email protected]>
218

319
* addr2line.c (fill_lines): compare the file names of object in which

NEWS

+6
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,9 @@ with all sufficient information, see the ChangeLog file.
8484

8585
* struct RBignum is hidden. [Feature #6083]
8686
Use rb_integer_pack and rb_integer_unpack instead.
87+
88+
* st hash table uses power-of-two sizes for speed [Feature #9425].
89+
Lookups are 10-25% faster if using appropriate hash functions.
90+
However, weaknesses in hash distribution can no longer be masked
91+
by prime number-sized tables, so extensions may need to tweak
92+
hash functions to ensure good distribution.

benchmark/bm_hash_aref_miss.rb

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

benchmark/bm_hash_aref_str.rb

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

benchmark/bm_hash_aref_sym.rb

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

benchmark/bm_hash_aref_sym_long.rb

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

benchmark/bm_hash_ident_num.rb

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
h = {}.compare_by_identity
2+
nums = (1..26).to_a
3+
nums.each { |n| h[n] = n }
4+
200_000.times { nums.each { |n| h[n] } }

benchmark/bm_hash_ident_obj.rb

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
h = {}.compare_by_identity
2+
objs = 26.times.map { Object.new }
3+
objs.each { |o| h[o] = o }
4+
200_000.times { objs.each { |o| h[o] } }

benchmark/bm_hash_ident_str.rb

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

benchmark/bm_hash_ident_sym.rb

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

hash.c

+1
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ rb_any_hash(VALUE a)
132132

133133
if (SPECIAL_CONST_P(a)) {
134134
if (a == Qundef) return 0;
135+
if (SYMBOL_P(a)) a >>= (RUBY_SPECIAL_SHIFT + 3); /* 3=ID_SCOPE_SHIFT */
135136
hnum = rb_objid_hash((st_index_t)a);
136137
}
137138
else if (BUILTIN_TYPE(a) == T_STRING) {

st.c

+17-57
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ typedef struct st_packed_entry {
3333
#define STATIC_ASSERT(name, expr) typedef int static_assert_##name##_check[(expr) ? 1 : -1];
3434

3535
#define ST_DEFAULT_MAX_DENSITY 5
36-
#define ST_DEFAULT_INIT_TABLE_SIZE 11
37-
#define ST_DEFAULT_SECOND_TABLE_SIZE 19
36+
#define ST_DEFAULT_INIT_TABLE_SIZE 16
3837
#define ST_DEFAULT_PACKED_TABLE_SIZE 18
3938
#define PACKED_UNIT (int)(sizeof(st_packed_entry) / sizeof(st_table_entry*))
4039
#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 *);
8584
#define EQUAL(table,x,y) ((x)==(y) || (*(table)->type->compare)((x),(y)) == 0)
8685

8786
#define do_hash(key,table) (st_index_t)(*(table)->type->hash)((key))
88-
#define hash_pos(h,n) ((h) % (n))
87+
#define hash_pos(h,n) ((h) & (n - 1))
8988
#define do_hash_bin(key,table) hash_pos(do_hash((key), (table)), (table)->num_bins)
9089

9190
/* preparation for possible allocation improvements */
@@ -140,69 +139,18 @@ remove_safe_packed_entry(st_table *table, st_index_t i, st_data_t never)
140139
PHASH_SET(table, i, 0);
141140
}
142141

143-
/*
144-
* MINSIZE is the minimum size of a dictionary.
145-
*/
146-
147-
#define MINSIZE 8
148-
149-
/*
150-
Table of prime numbers 2^n+a, 2<=n<=30.
151-
*/
152-
static const unsigned int primes[] = {
153-
ST_DEFAULT_INIT_TABLE_SIZE,
154-
ST_DEFAULT_SECOND_TABLE_SIZE,
155-
32 + 5,
156-
64 + 3,
157-
128 + 3,
158-
256 + 27,
159-
512 + 9,
160-
1024 + 9,
161-
2048 + 5,
162-
4096 + 3,
163-
8192 + 27,
164-
16384 + 43,
165-
32768 + 3,
166-
65536 + 45,
167-
131072 + 29,
168-
262144 + 3,
169-
524288 + 21,
170-
1048576 + 7,
171-
2097152 + 17,
172-
4194304 + 15,
173-
8388608 + 9,
174-
16777216 + 43,
175-
33554432 + 35,
176-
67108864 + 15,
177-
134217728 + 29,
178-
268435456 + 3,
179-
536870912 + 11,
180-
1073741824 + 85,
181-
0
182-
};
183-
184142
static st_index_t
185143
new_size(st_index_t size)
186144
{
187-
int i;
145+
st_index_t i;
188146

189-
#if 0
190147
for (i=3; i<31; i++) {
191-
if ((1<<i) > size) return 1<<i;
192-
}
193-
return -1;
194-
#else
195-
st_index_t newsize;
196-
197-
for (i = 0, newsize = MINSIZE; i < numberof(primes); i++, newsize <<= 1) {
198-
if (newsize > size) return primes[i];
148+
if ((st_index_t)(1<<i) > size) return 1<<i;
199149
}
200-
/* Ran out of primes */
201150
#ifndef NOT_RUBY
202151
rb_raise(rb_eRuntimeError, "st_table too big");
203152
#endif
204153
return -1; /* should raise exception */
205-
#endif
206154
}
207155

208156
#ifdef HASH_LOG
@@ -1685,5 +1633,17 @@ st_numcmp(st_data_t x, st_data_t y)
16851633
st_index_t
16861634
st_numhash(st_data_t n)
16871635
{
1688-
return (st_index_t)n;
1636+
/*
1637+
* This hash function is lightly-tuned for Ruby. Further tuning
1638+
* should be possible. Notes:
1639+
*
1640+
* - (n >> 3) alone is great for heap objects and OK for fixnum,
1641+
* however symbols perform poorly.
1642+
* - (n >> (RUBY_SPECIAL_SHIFT+3)) was added to make symbols hash well,
1643+
* n.b.: +3 to remove ID scope, +1 worked well initially, too
1644+
* - (n << 3) was finally added to avoid losing bits for fixnums
1645+
* - avoid expensive modulo instructions, it is currently only
1646+
* shifts and bitmask operations.
1647+
*/
1648+
return (st_index_t)((n>>(RUBY_SPECIAL_SHIFT+3)|(n<<3)) ^ (n>>3));
16891649
}

0 commit comments

Comments
 (0)