Skip to content

Commit cfec60d

Browse files
committed
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 c091c18) +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 fcd2100) +PRISM [aarch64-linux] built-ruby: ruby 4.1.0dev (2026-04-09T06:38:09Z get-cwd-cache 6774af9) +PRISM [aarch64-linux] ``` | |compare-ruby|built-ruby| |:--------|-----------:|---------:| |Dir.pwd | 4.157M| 5.541M| | | -| 1.33x|
1 parent b154cfa commit cfec60d

File tree

2 files changed

+31
-3
lines changed

2 files changed

+31
-3
lines changed

benchmark/dir_pwd.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
benchmark:
2+
pwd: Dir.pwd

dir.c

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1585,6 +1585,8 @@ dir_chdir(VALUE dir)
15851585
#endif
15861586
}
15871587

1588+
static VALUE last_cwd;
1589+
15881590
#ifndef _WIN32
15891591
static VALUE
15901592
getcwd_to_str(VALUE arg)
@@ -1604,20 +1606,43 @@ getcwd_xfree(VALUE arg)
16041606
return Qnil;
16051607
}
16061608

1607-
VALUE
1608-
rb_dir_getwd_ospath(void)
1609+
static VALUE
1610+
rb_dir_getwd_ospath_slowpath(void)
16091611
{
16101612
char *path = ruby_getcwd();
16111613
return rb_ensure(getcwd_to_str, (VALUE)path, getcwd_xfree, (VALUE)path);
16121614
}
1615+
1616+
VALUE
1617+
rb_dir_getwd_ospath(void)
1618+
{
1619+
char buf[PATH_MAX];
1620+
char *path = getcwd(buf, PATH_MAX);
1621+
if (!path) {
1622+
return rb_dir_getwd_ospath_slowpath();
1623+
}
1624+
1625+
VALUE cached_cwd = RUBY_ATOMIC_VALUE_LOAD(last_cwd);
1626+
1627+
if (!cached_cwd || strcmp(RSTRING_PTR(cached_cwd), path) != 0) {
1628+
#ifdef __APPLE__
1629+
cached_cwd = rb_str_normalize_ospath(path, strlen(path));
1630+
#else
1631+
cached_cwd = rb_str_new2(path);
1632+
#endif
1633+
rb_str_freeze(cached_cwd);
1634+
RUBY_ATOMIC_VALUE_SET(last_cwd, cached_cwd);
1635+
}
1636+
return cached_cwd;
1637+
}
16131638
#endif
16141639

16151640
VALUE
16161641
rb_dir_getwd(void)
16171642
{
16181643
rb_encoding *fs = rb_filesystem_encoding();
16191644
int fsenc = rb_enc_to_index(fs);
1620-
VALUE cwd = rb_dir_getwd_ospath();
1645+
VALUE cwd = rb_str_new_shared(rb_dir_getwd_ospath());
16211646

16221647
switch (fsenc) {
16231648
case ENCINDEX_US_ASCII:
@@ -4008,6 +4033,7 @@ Init_Dir(void)
40084033

40094034
rb_gc_register_address(&chdir_lock.path);
40104035
rb_gc_register_address(&chdir_lock.thread);
4036+
rb_gc_register_address(&last_cwd);
40114037

40124038
rb_cDir = rb_define_class("Dir", rb_cObject);
40134039

0 commit comments

Comments
 (0)