diff options
| author | Jean Boussier <jean.boussier@gmail.com> | 2026-01-21 20:33:22 +0100 |
|---|---|---|
| committer | Jean Boussier <jean.boussier@gmail.com> | 2026-04-10 11:49:58 +0200 |
| commit | cfec60d4bec0d99e17ed56bea7055de7ec7674a1 (patch) | |
| tree | cb306cbbee37b28675a4a035601ec5730062613a | |
| parent | b154cfa98c01b17b8f4df82ebb7a5bc9ab7bf7bb (diff) | |
dir.c: cache and revalidate working directory
`rb_dir_getwd_ospath()` is called quite frequently, but
the overwhelming majority of the time, the current directory
didn't change.
We can also assume that most of the time, `PATH_MAX` is enough
for `getcwd`, hence we can first attempt to use a small stack
buffer rather than always allocate on the heap.
This way we can keep the last `pwd` and revalidate it with no
allocation.
On macOS syscalls are fairly slow, so the gain isn't very large.
macOS:
```
compare-ruby: ruby 4.1.0dev (2026-04-09T05:19:02Z master c091c186e4) +PRISM [arm64-darwin25]
built-ruby: ruby 4.1.0dev (2026-04-09T06:37:20Z get-cwd-cache ea02126d79) +PRISM [arm64-darwin25]
```
| |compare-ruby|built-ruby|
|:--------|-----------:|---------:|
|Dir.pwd | 105.183k| 113.420k|
| | -| 1.08x|
```
Linux (inside virtualized Docker)
```
compare-ruby: ruby 4.1.0dev (2026-04-07T08:26:25Z master fcd210086c) +PRISM [aarch64-linux]
built-ruby: ruby 4.1.0dev (2026-04-09T06:38:09Z get-cwd-cache 6774af9ba7) +PRISM [aarch64-linux]
```
| |compare-ruby|built-ruby|
|:--------|-----------:|---------:|
|Dir.pwd | 4.157M| 5.541M|
| | -| 1.33x|
| -rw-r--r-- | benchmark/dir_pwd.yml | 2 | ||||
| -rw-r--r-- | dir.c | 32 |
2 files changed, 31 insertions, 3 deletions
diff --git a/benchmark/dir_pwd.yml b/benchmark/dir_pwd.yml new file mode 100644 index 0000000000..c435d3ac5e --- /dev/null +++ b/benchmark/dir_pwd.yml @@ -0,0 +1,2 @@ +benchmark: + pwd: Dir.pwd @@ -1585,6 +1585,8 @@ dir_chdir(VALUE dir) #endif } +static VALUE last_cwd; + #ifndef _WIN32 static VALUE getcwd_to_str(VALUE arg) @@ -1604,12 +1606,35 @@ getcwd_xfree(VALUE arg) return Qnil; } -VALUE -rb_dir_getwd_ospath(void) +static VALUE +rb_dir_getwd_ospath_slowpath(void) { char *path = ruby_getcwd(); return rb_ensure(getcwd_to_str, (VALUE)path, getcwd_xfree, (VALUE)path); } + +VALUE +rb_dir_getwd_ospath(void) +{ + char buf[PATH_MAX]; + char *path = getcwd(buf, PATH_MAX); + if (!path) { + return rb_dir_getwd_ospath_slowpath(); + } + + VALUE cached_cwd = RUBY_ATOMIC_VALUE_LOAD(last_cwd); + + if (!cached_cwd || strcmp(RSTRING_PTR(cached_cwd), path) != 0) { +#ifdef __APPLE__ + cached_cwd = rb_str_normalize_ospath(path, strlen(path)); +#else + cached_cwd = rb_str_new2(path); +#endif + rb_str_freeze(cached_cwd); + RUBY_ATOMIC_VALUE_SET(last_cwd, cached_cwd); + } + return cached_cwd; +} #endif VALUE @@ -1617,7 +1642,7 @@ rb_dir_getwd(void) { rb_encoding *fs = rb_filesystem_encoding(); int fsenc = rb_enc_to_index(fs); - VALUE cwd = rb_dir_getwd_ospath(); + VALUE cwd = rb_str_new_shared(rb_dir_getwd_ospath()); switch (fsenc) { case ENCINDEX_US_ASCII: @@ -4008,6 +4033,7 @@ Init_Dir(void) rb_gc_register_address(&chdir_lock.path); rb_gc_register_address(&chdir_lock.thread); + rb_gc_register_address(&last_cwd); rb_cDir = rb_define_class("Dir", rb_cObject); |
