diff options
| author | Kerem Kat <keremkat@gmail.com> | 2023-07-29 06:27:11 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-07-29 05:27:11 +0000 |
| commit | 8346d1630b8193eef1ec9dd537b16de74afdc2e8 (patch) | |
| tree | 666760eeb61b72fac0d01e35b847321a1e77fbae | |
| parent | 19a3466a1460924058ca16a259601bb753293d43 (diff) | |
Ruby 3.2 - Speed up rebuilding the loaded feature index and realpath cache (#8023)
* Speed up rebuilding the loaded feature index
Rebuilding the loaded feature index slowed down with the bug fix
for #17885 in 79a4484a072e9769b603e7b4fbdb15b1d7eccb15. The
slowdown was extreme if realpath emulation was used, but even when
not emulated, it could be about 10x slower.
This adds loaded_features_realpath_map to rb_vm_struct. This is a
hidden hash mapping loaded feature paths to realpaths. When
rebuilding the loaded feature index, look at this hash to get
cached realpath values, and skip calling rb_check_realpath if a
cached value is found.
Fixes [Bug #19246]
* Add a realpath cache to reduce number of syscalls.
Number of lstat and stat syscalls for each 'require'd file is doubled,
because rb_realpath_internal is called from two places with the same
arguments in require_internal; once for checking the realpaths cache,
and once in load_iseq_eval when iseq is not found.
Introduce rb_realpath_internal_cached function to reuse the realpath_map
cache which memoizes rb_realpath_internal function, leading to less
syscalls and increased startup performance depending on the cost of the syscalls
in a particular environment.
---------
Co-authored-by: Jeremy Evans <code@jeremyevans.net>
| -rw-r--r-- | load.c | 47 | ||||
| -rw-r--r-- | vm.c | 2 | ||||
| -rw-r--r-- | vm_core.h | 1 |
3 files changed, 44 insertions, 6 deletions
@@ -164,6 +164,12 @@ get_loaded_features_realpaths(rb_vm_t *vm) } static VALUE +get_loaded_features_realpath_map(rb_vm_t *vm) +{ + return vm->loaded_features_realpath_map; +} + +static VALUE get_LOADED_FEATURES(ID _x, VALUE *_y) { return get_loaded_features(GET_VM()); @@ -361,7 +367,10 @@ get_loaded_features_index(rb_vm_t *vm) st_foreach(vm->loaded_features_index, loaded_features_index_clear_i, 0); VALUE realpaths = vm->loaded_features_realpaths; + VALUE realpath_map = vm->loaded_features_realpath_map; + VALUE previous_realpath_map = rb_hash_dup(realpath_map); rb_hash_clear(realpaths); + rb_hash_clear(realpath_map); features = vm->loaded_features; for (i = 0; i < RARRAY_LEN(features); i++) { VALUE entry, as_str; @@ -378,9 +387,14 @@ get_loaded_features_index(rb_vm_t *vm) long j = RARRAY_LEN(features); for (i = 0; i < j; i++) { VALUE as_str = rb_ary_entry(features, i); - VALUE realpath = rb_check_realpath(Qnil, as_str, NULL); - if (NIL_P(realpath)) realpath = as_str; - rb_hash_aset(realpaths, rb_fstring(realpath), Qtrue); + VALUE realpath = rb_hash_aref(previous_realpath_map, as_str); + if (NIL_P(realpath)) { + realpath = rb_check_realpath(Qnil, as_str, NULL); + if (NIL_P(realpath)) realpath = as_str; + realpath = rb_fstring(realpath); + } + rb_hash_aset(realpaths, realpath, Qtrue); + rb_hash_aset(realpath_map, as_str, realpath); } } return vm->loaded_features_index; @@ -675,6 +689,19 @@ rb_provide(const char *feature) NORETURN(static void load_failed(VALUE)); +static inline VALUE +realpath_internal_cached(VALUE hash, VALUE path) +{ + VALUE ret = rb_hash_aref(hash, path); + if(RTEST(ret)) { + return ret; + } + + VALUE realpath = rb_realpath_internal(Qnil, path, 1); + rb_hash_aset(hash, rb_fstring(path), rb_fstring(realpath)); + return realpath; +} + static inline void load_iseq_eval(rb_execution_context_t *ec, VALUE fname) { @@ -687,8 +714,12 @@ load_iseq_eval(rb_execution_context_t *ec, VALUE fname) VALUE parser = rb_parser_new(); rb_parser_set_context(parser, NULL, FALSE); ast = (rb_ast_t *)rb_parser_load_file(parser, fname); + + rb_thread_t *th = rb_ec_thread_ptr(ec); + VALUE realpath_map = get_loaded_features_realpath_map(th->vm); + iseq = rb_iseq_new_top(&ast->body, rb_fstring_lit("<top (required)>"), - fname, rb_realpath_internal(Qnil, fname, 1), NULL); + fname, realpath_internal_cached(realpath_map, fname), NULL); rb_ast_dispose(ast); rb_vm_pop_frame(ec); RB_GC_GUARD(v); @@ -1161,6 +1192,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa volatile VALUE saved_path; volatile VALUE realpath = 0; VALUE realpaths = get_loaded_features_realpaths(th->vm); + VALUE realpath_map = get_loaded_features_realpath_map(th->vm); volatile bool reset_ext_config = false; struct rb_ext_config prev_ext_config; @@ -1194,7 +1226,7 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa } #endif else if (RTEST(rb_hash_aref(realpaths, - realpath = rb_realpath_internal(Qnil, path, 1)))) { + realpath = realpath_internal_cached(realpath_map, path)))) { result = 0; } else { @@ -1252,7 +1284,8 @@ require_internal(rb_execution_context_t *ec, VALUE fname, int exception, bool wa rb_provide_feature(th2->vm, path); VALUE real = realpath; if (real) { - rb_hash_aset(realpaths, rb_fstring(real), Qtrue); + real = rb_fstring(real); + rb_hash_aset(realpaths, real, Qtrue); } } ec->errinfo = saved.errinfo; @@ -1473,6 +1506,8 @@ Init_load(void) vm->loaded_features_index = st_init_numtable(); vm->loaded_features_realpaths = rb_hash_new(); rb_obj_hide(vm->loaded_features_realpaths); + vm->loaded_features_realpath_map = rb_hash_new(); + rb_obj_hide(vm->loaded_features_realpath_map); rb_define_global_function("load", rb_f_load, -1); rb_define_global_function("require", rb_f_require, 1); @@ -2703,6 +2703,7 @@ rb_vm_update_references(void *ptr) vm->loaded_features = rb_gc_location(vm->loaded_features); vm->loaded_features_snapshot = rb_gc_location(vm->loaded_features_snapshot); vm->loaded_features_realpaths = rb_gc_location(vm->loaded_features_realpaths); + vm->loaded_features_realpath_map = rb_gc_location(vm->loaded_features_realpath_map); vm->top_self = rb_gc_location(vm->top_self); vm->orig_progname = rb_gc_location(vm->orig_progname); @@ -2794,6 +2795,7 @@ rb_vm_mark(void *ptr) rb_gc_mark_movable(vm->loaded_features); rb_gc_mark_movable(vm->loaded_features_snapshot); rb_gc_mark_movable(vm->loaded_features_realpaths); + rb_gc_mark_movable(vm->loaded_features_realpath_map); rb_gc_mark_movable(vm->top_self); rb_gc_mark_movable(vm->orig_progname); RUBY_MARK_MOVABLE_UNLESS_NULL(vm->coverages); @@ -680,6 +680,7 @@ typedef struct rb_vm_struct { VALUE loaded_features; VALUE loaded_features_snapshot; VALUE loaded_features_realpaths; + VALUE loaded_features_realpath_map; struct st_table *loaded_features_index; struct st_table *loading_table; #if EXTSTATIC |
