[Bug #19969] Compact st_table after deleted if possible

This commit is contained in:
Nobuyoshi Nakada 2023-10-24 18:33:14 +09:00
Родитель 2a442121d1
Коммит 9eac9d7178
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 3582D74E1FEE4465
3 изменённых файлов: 57 добавлений и 11 удалений

19
hash.c
Просмотреть файл

@ -1423,6 +1423,16 @@ rb_hash_foreach(VALUE hash, rb_foreach_func *func, VALUE farg)
hash_verify(hash); hash_verify(hash);
} }
void rb_st_compact_table(st_table *tab);
static void
compact_after_delete(VALUE hash)
{
if (RHASH_ITER_LEV(hash) == 0 && RHASH_ST_TABLE_P(hash)) {
rb_st_compact_table(RHASH_ST_TABLE(hash));
}
}
static VALUE static VALUE
hash_alloc_flags(VALUE klass, VALUE flags, VALUE ifnone, bool st) hash_alloc_flags(VALUE klass, VALUE flags, VALUE ifnone, bool st)
{ {
@ -2397,6 +2407,7 @@ rb_hash_delete_m(VALUE hash, VALUE key)
val = rb_hash_delete_entry(hash, key); val = rb_hash_delete_entry(hash, key);
if (!UNDEF_P(val)) { if (!UNDEF_P(val)) {
compact_after_delete(hash);
return val; return val;
} }
else { else {
@ -2517,6 +2528,7 @@ rb_hash_delete_if(VALUE hash)
rb_hash_modify_check(hash); rb_hash_modify_check(hash);
if (!RHASH_TABLE_EMPTY_P(hash)) { if (!RHASH_TABLE_EMPTY_P(hash)) {
rb_hash_foreach(hash, delete_if_i, hash); rb_hash_foreach(hash, delete_if_i, hash);
compact_after_delete(hash);
} }
return hash; return hash;
} }
@ -2580,6 +2592,7 @@ rb_hash_reject(VALUE hash)
result = hash_dup_with_compare_by_id(hash); result = hash_dup_with_compare_by_id(hash);
if (!RHASH_EMPTY_P(hash)) { if (!RHASH_EMPTY_P(hash)) {
rb_hash_foreach(result, delete_if_i, result); rb_hash_foreach(result, delete_if_i, result);
compact_after_delete(result);
} }
return result; return result;
} }
@ -2639,6 +2652,7 @@ rb_hash_except(int argc, VALUE *argv, VALUE hash)
key = argv[i]; key = argv[i];
rb_hash_delete(result, key); rb_hash_delete(result, key);
} }
compact_after_delete(result);
return result; return result;
} }
@ -2734,6 +2748,7 @@ rb_hash_select(VALUE hash)
result = hash_dup_with_compare_by_id(hash); result = hash_dup_with_compare_by_id(hash);
if (!RHASH_EMPTY_P(hash)) { if (!RHASH_EMPTY_P(hash)) {
rb_hash_foreach(result, keep_if_i, result); rb_hash_foreach(result, keep_if_i, result);
compact_after_delete(result);
} }
return result; return result;
} }
@ -2823,6 +2838,7 @@ rb_hash_clear(VALUE hash)
} }
else { else {
st_clear(RHASH_ST_TABLE(hash)); st_clear(RHASH_ST_TABLE(hash));
compact_after_delete(hash);
} }
return hash; return hash;
@ -3253,6 +3269,7 @@ rb_hash_transform_keys_bang(int argc, VALUE *argv, VALUE hash)
rb_ary_clear(pairs); rb_ary_clear(pairs);
rb_hash_clear(new_keys); rb_hash_clear(new_keys);
} }
compact_after_delete(hash);
return hash; return hash;
} }
@ -3303,6 +3320,7 @@ rb_hash_transform_values(VALUE hash)
if (!RHASH_EMPTY_P(hash)) { if (!RHASH_EMPTY_P(hash)) {
rb_hash_stlike_foreach_with_replace(result, transform_values_foreach_func, transform_values_foreach_replace, result); rb_hash_stlike_foreach_with_replace(result, transform_values_foreach_func, transform_values_foreach_replace, result);
compact_after_delete(result);
} }
return result; return result;
@ -4267,6 +4285,7 @@ rb_hash_compact(VALUE hash)
VALUE result = rb_hash_dup(hash); VALUE result = rb_hash_dup(hash);
if (!RHASH_EMPTY_P(hash)) { if (!RHASH_EMPTY_P(hash)) {
rb_hash_foreach(result, delete_if_nil, result); rb_hash_foreach(result, delete_if_nil, result);
compact_after_delete(result);
} }
else if (rb_hash_compare_by_id_p(hash)) { else if (rb_hash_compare_by_id_p(hash)) {
result = rb_hash_compare_by_id(result); result = rb_hash_compare_by_id(result);

40
st.c
Просмотреть файл

@ -717,6 +717,8 @@ count_collision(const struct st_hash_type *type)
#error "REBUILD_THRESHOLD should be >= 2" #error "REBUILD_THRESHOLD should be >= 2"
#endif #endif
static void rebuild_table_with(st_table *new_tab, st_table *tab);
/* Rebuild table TAB. Rebuilding removes all deleted bins and entries /* Rebuild table TAB. Rebuilding removes all deleted bins and entries
and can change size of the table entries and bins arrays. and can change size of the table entries and bins arrays.
Rebuilding is implemented by creation of a new table or by Rebuilding is implemented by creation of a new table or by
@ -724,14 +726,6 @@ count_collision(const struct st_hash_type *type)
static void static void
rebuild_table(st_table *tab) rebuild_table(st_table *tab)
{ {
st_index_t i, ni;
unsigned int size_ind;
st_table *new_tab;
st_table_entry *new_entries;
st_table_entry *curr_entry_ptr;
st_index_t *bins;
st_index_t bin_ind;
if ((2 * tab->num_entries <= get_allocated_entries(tab) if ((2 * tab->num_entries <= get_allocated_entries(tab)
&& REBUILD_THRESHOLD * tab->num_entries > get_allocated_entries(tab)) && REBUILD_THRESHOLD * tab->num_entries > get_allocated_entries(tab))
|| tab->num_entries < (1 << MINIMAL_POWER2)) { || tab->num_entries < (1 << MINIMAL_POWER2)) {
@ -739,17 +733,30 @@ rebuild_table(st_table *tab)
tab->num_entries = 0; tab->num_entries = 0;
if (tab->bins != NULL) if (tab->bins != NULL)
initialize_bins(tab); initialize_bins(tab);
new_tab = tab; rebuild_table_with(tab, tab);
new_entries = tab->entries;
} }
else { else {
st_table *new_tab;
/* This allocation could trigger GC and compaction. If tab is the /* This allocation could trigger GC and compaction. If tab is the
* gen_iv_tbl, then tab could have changed in size due to objects being * gen_iv_tbl, then tab could have changed in size due to objects being
* freed and/or moved. Do not store attributes of tab before this line. */ * freed and/or moved. Do not store attributes of tab before this line. */
new_tab = st_init_table_with_size(tab->type, new_tab = st_init_table_with_size(tab->type,
2 * tab->num_entries - 1); 2 * tab->num_entries - 1);
new_entries = new_tab->entries; rebuild_table_with(new_tab, tab);
} }
}
static void
rebuild_table_with(st_table *new_tab, st_table *tab)
{
st_index_t i, ni;
unsigned int size_ind;
st_table_entry *new_entries;
st_table_entry *curr_entry_ptr;
st_index_t *bins;
st_index_t bin_ind;
new_entries = new_tab->entries;
ni = 0; ni = 0;
bins = new_tab->bins; bins = new_tab->bins;
@ -2296,4 +2303,15 @@ rb_st_nth_key(st_table *tab, st_index_t index)
} }
} }
void
rb_st_compact_table(st_table *tab)
{
st_index_t num = tab->num_entries;
if (REBUILD_THRESHOLD * num <= get_allocated_entries(tab)) {
/* Compaction: */
st_table *new_tab = st_init_table_with_size(tab->type, 2 * num);
rebuild_table_with(new_tab, tab);
}
}
#endif #endif

Просмотреть файл

@ -1792,6 +1792,15 @@ class TestHash < Test::Unit::TestCase
assert_no_memory_leak([], prepare, code, bug9187) assert_no_memory_leak([], prepare, code, bug9187)
end end
def test_memory_size_after_delete
require 'objspace'
h = {}
1000.times {|i| h[i] = true}
big = ObjectSpace.memsize_of(h)
1000.times {|i| h.delete(i)}
assert_operator ObjectSpace.memsize_of(h), :<, big/10
end
def test_wrapper def test_wrapper
bug9381 = '[ruby-core:59638] [Bug #9381]' bug9381 = '[ruby-core:59638] [Bug #9381]'