diff options
| author | U.Nakamura <usa@ruby-lang.org> | 2023-11-20 21:11:21 +0900 |
|---|---|---|
| committer | U.Nakamura <usa@ruby-lang.org> | 2023-11-20 21:11:21 +0900 |
| commit | 1cae5e7ceaca7304108fdec35d4858a9e4ff7fe0 (patch) | |
| tree | 23053c40ef7b39a13364b836837cda53362c6d8b | |
| parent | 0880158f6bbbe6eaf35b6cc866016fec7610ba2e (diff) | |
merge revision(s) 9eac9d71786a8dbec520d0541a91149f01adf8ea: [Backport #19969]
[Bug #19969] Compact st_table after deleted if possible
---
hash.c | 19 +++++++++++++++++++
st.c | 40 +++++++++++++++++++++++++++++-----------
test/ruby/test_hash.rb | 9 +++++++++
3 files changed, 57 insertions(+), 11 deletions(-)
| -rw-r--r-- | hash.c | 18 | ||||
| -rw-r--r-- | st.c | 40 | ||||
| -rw-r--r-- | test/ruby/test_hash.rb | 9 | ||||
| -rw-r--r-- | version.h | 4 |
4 files changed, 58 insertions, 13 deletions
@@ -1519,6 +1519,16 @@ rb_hash_foreach(VALUE hash, rb_foreach_func *func, VALUE farg) 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 hash_alloc_flags(VALUE klass, VALUE flags, VALUE ifnone) { @@ -2426,6 +2436,7 @@ rb_hash_delete_m(VALUE hash, VALUE key) val = rb_hash_delete_entry(hash, key); if (val != Qundef) { + compact_after_delete(hash); return val; } else { @@ -2547,6 +2558,7 @@ rb_hash_delete_if(VALUE hash) rb_hash_modify_check(hash); if (!RHASH_TABLE_EMPTY_P(hash)) { rb_hash_foreach(hash, delete_if_i, hash); + compact_after_delete(hash); } return hash; } @@ -2610,6 +2622,7 @@ rb_hash_reject(VALUE hash) result = hash_dup_with_compare_by_id(hash); if (!RHASH_EMPTY_P(hash)) { rb_hash_foreach(result, delete_if_i, result); + compact_after_delete(result); } return result; } @@ -2669,6 +2682,7 @@ rb_hash_except(int argc, VALUE *argv, VALUE hash) key = argv[i]; rb_hash_delete(result, key); } + compact_after_delete(result); return result; } @@ -2766,6 +2780,7 @@ rb_hash_select(VALUE hash) result = hash_dup_with_compare_by_id(hash); if (!RHASH_EMPTY_P(hash)) { rb_hash_foreach(result, keep_if_i, result); + compact_after_delete(result); } return result; } @@ -2857,6 +2872,7 @@ rb_hash_clear(VALUE hash) } else { st_clear(RHASH_ST_TABLE(hash)); + compact_after_delete(hash); } return hash; @@ -3302,6 +3318,7 @@ rb_hash_transform_keys_bang(int argc, VALUE *argv, VALUE hash) rb_ary_clear(pairs); rb_hash_clear(new_keys); } + compact_after_delete(hash); return hash; } @@ -3352,6 +3369,7 @@ rb_hash_transform_values(VALUE hash) if (!RHASH_EMPTY_P(hash)) { rb_hash_stlike_foreach_with_replace(result, transform_values_foreach_func, transform_values_foreach_replace, result); + compact_after_delete(result); } return result; @@ -696,6 +696,8 @@ count_collision(const struct st_hash_type *type) #error "REBUILD_THRESHOLD should be >= 2" #endif +static void rebuild_table_with(st_table *new_tab, st_table *tab); + /* Rebuild table TAB. Rebuilding removes all deleted bins and entries and can change size of the table entries and bins arrays. Rebuilding is implemented by creation of a new table or by @@ -703,14 +705,6 @@ count_collision(const struct st_hash_type *type) static void 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) && REBUILD_THRESHOLD * tab->num_entries > get_allocated_entries(tab)) || tab->num_entries < (1 << MINIMAL_POWER2)) { @@ -718,17 +712,30 @@ rebuild_table(st_table *tab) tab->num_entries = 0; if (tab->bins != NULL) initialize_bins(tab); - new_tab = tab; - new_entries = tab->entries; + rebuild_table_with(tab, tab); } else { + st_table *new_tab; /* 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 * freed and/or moved. Do not store attributes of tab before this line. */ new_tab = st_init_table_with_size(tab->type, 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; bins = new_tab->bins; @@ -2265,4 +2272,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 diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index eaff09d434..683ec3855d 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -1733,6 +1733,15 @@ class TestHash < Test::Unit::TestCase assert_no_memory_leak([], prepare, code, bug9187) 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 bug9381 = '[ruby-core:59638] [Bug #9381]' @@ -11,11 +11,11 @@ # define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR #define RUBY_VERSION_TEENY 4 #define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR -#define RUBY_PATCHLEVEL 248 +#define RUBY_PATCHLEVEL 249 #define RUBY_RELEASE_YEAR 2023 #define RUBY_RELEASE_MONTH 11 -#define RUBY_RELEASE_DAY 14 +#define RUBY_RELEASE_DAY 20 #include "ruby/version.h" |
