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|
This commit is contained in:
Jean Boussier 2026-01-21 20:33:22 +01:00
parent b154cfa98c
commit cfec60d4be
Notes: git 2026-04-10 09:50:24 +00:00
2 changed files with 31 additions and 3 deletions

2
benchmark/dir_pwd.yml Normal file
View File

@ -0,0 +1,2 @@
benchmark:
pwd: Dir.pwd

32
dir.c
View File

@ -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);