Skip to content

Speed up realpath on Linux and macOS#3838

Open
jakebailey wants to merge 7 commits into
mainfrom
jabaile/faster-realpath
Open

Speed up realpath on Linux and macOS#3838
jakebailey wants to merge 7 commits into
mainfrom
jabaile/faster-realpath

Conversation

@jakebailey
Copy link
Copy Markdown
Member

On Linux, we can open a file with O_PATH to ask it to be opened only for path resolution, then check /proc/self/fd/<fd> for the finalized path in one go. This avoids EvalSymlinks entirely, greatly speeding up realpath.

goos: linux
goarch: amd64
pkg: github.com/microsoft/typescript-go/internal/vfs/osvfs
cpu: Intel(R) Core(TM) i9-10900K CPU @ 3.70GHz
                              │   old.txt    │               new.txt               │
                              │    sec/op    │   sec/op     vs base                │
Realpath/target-20               4.968µ ± 2%   3.413µ ± 1%  -31.29% (p=0.000 n=10)
Realpath/link-20                 9.703µ ± 1%   3.661µ ± 1%  -62.27% (p=0.000 n=10)
Realpath/deep-20                18.681µ ± 0%   4.074µ ± 1%  -78.19% (p=0.000 n=10)
Realpath/deep_evalSymlinks-20    18.32µ ± 1%   18.21µ ± 1%        ~ (p=0.105 n=10)
geomean                          11.33µ        5.518µ       -51.31%
                              │   old.txt    │                new.txt                 │
                              │     B/op     │     B/op      vs base                  │
Realpath/target-20               1584.0 ± 0%     128.0 ± 0%  -91.92% (p=0.000 n=10)
Realpath/link-20                 3072.0 ± 0%     112.0 ± 0%  -96.35% (p=0.000 n=10)
Realpath/deep-20                 6720.0 ± 0%     272.0 ± 0%  -95.95% (p=0.000 n=10)
Realpath/deep_evalSymlinks-20   6.562Ki ± 0%   6.562Ki ± 0%        ~ (p=1.000 n=10) ¹
geomean                         3.760Ki          402.3       -89.55%
¹ all samples are equal
                              │   old.txt   │               new.txt                │
                              │  allocs/op  │ allocs/op   vs base                  │
Realpath/target-20              19.000 ± 0%   3.000 ± 0%  -84.21% (p=0.000 n=10)
Realpath/link-20                38.000 ± 0%   3.000 ± 0%  -92.11% (p=0.000 n=10)
Realpath/deep-20                59.000 ± 0%   3.000 ± 0%  -94.92% (p=0.000 n=10)
Realpath/deep_evalSymlinks-20    59.00 ± 0%   59.00 ± 0%        ~ (p=1.000 n=10) ¹
geomean                          39.82        6.318       -84.13%
¹ all samples are equal

On macOS, we can do a similar thing using fcntl(F_GETPATH). This is not that drastic, but is still faster for unlinked files, the common case.

goos: darwin
goarch: arm64
pkg: github.com/microsoft/typescript-go/internal/vfs/osvfs
cpu: Apple M1
                             │   old.txt    │               new.txt               │
                             │    sec/op    │   sec/op     vs base                │
Realpath/target-8              140.3µ ± 28%   115.8µ ± 0%  -17.48% (p=0.000 n=10)
Realpath/link-8                116.8µ ± 56%   116.4µ ± 0%        ~ (p=0.631 n=10)
Realpath/deep-8                120.4µ ±  0%   119.5µ ± 0%   -0.70% (p=0.000 n=10)
Realpath/deep_evalSymlinks-8   26.25µ ±  0%   26.34µ ± 0%   +0.32% (p=0.001 n=10)
geomean                        84.83µ         80.70µ        -4.86%
                             │   old.txt    │                new.txt                │
                             │     B/op     │     B/op      vs base                 │
Realpath/target-8                208.0 ± 0%     208.0 ± 0%       ~ (p=1.000 n=10) ¹
Realpath/link-8                  208.0 ± 0%     208.0 ± 0%       ~ (p=1.000 n=10) ¹
Realpath/deep-8                  368.0 ± 0%     368.0 ± 0%       ~ (p=1.000 n=10) ¹
Realpath/deep_evalSymlinks-8   10.83Ki ± 0%   10.83Ki ± 0%       ~ (p=1.000 n=10) ¹
geomean                          648.2          648.2       +0.00%
¹ all samples are equal
                             │  old.txt   │               new.txt               │
                             │ allocs/op  │ allocs/op   vs base                 │
Realpath/target-8              2.000 ± 0%   2.000 ± 0%       ~ (p=1.000 n=10) ¹
Realpath/link-8                2.000 ± 0%   2.000 ± 0%       ~ (p=1.000 n=10) ¹
Realpath/deep-8                2.000 ± 0%   2.000 ± 0%       ~ (p=1.000 n=10) ¹
Realpath/deep_evalSymlinks-8   86.00 ± 0%   86.00 ± 0%       ~ (p=1.000 n=10) ¹
geomean                        5.121        5.121       +0.00%
¹ all samples are equal

Other OSs might have something like this, but now we have special cases for Windows, Linux, and macOS, which is most everyone.

Copilot AI review requested due to automatic review settings May 14, 2026 01:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Speeds up osFS.Realpath on Linux and macOS by replacing filepath.EvalSymlinks (which does an lstat per path component) with O(1)-syscall canonicalization: open(O_PATH) + readlink(/proc/self/fd/N) on Linux and open(O_EVTONLY) + fcntl(F_GETPATH) on macOS. Both implementations probe for kernel/OS support once and fall back to EvalSymlinks when unavailable. A shared EINTR retry helper is introduced, and a deeper benchmark case is added.

Changes:

  • Add realpath_linux.go using O_PATH + /proc/self/fd/<fd> readlink, with a sync.OnceValue probe for procfs availability.
  • Add realpath_darwin.go using O_EVTONLY + fcntl(F_GETPATH), with a sync.OnceValue probe for F_GETPATH support.
  • Restrict realpath_other.go to non-Linux/non-Darwin Unix, add EINTR retry helpers in eintr_unix.go, and extend realpath_test.go with a deep-path benchmark and an EvalSymlinks baseline.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/vfs/osvfs/realpath_linux.go New Linux fast-path realpath via O_PATH and /proc/self/fd.
internal/vfs/osvfs/realpath_darwin.go New macOS fast-path realpath via O_EVTONLY and fcntl(F_GETPATH).
internal/vfs/osvfs/realpath_other.go Narrow build constraint so Linux/Darwin use the new specialized files.
internal/vfs/osvfs/eintr_unix.go New EINTR retry helpers (ignoringEINTR, ignoringEINTR2) for non-Windows builds.
internal/vfs/osvfs/realpath_test.go Add deep and deep_evalSymlinks benchmarks to demonstrate scaling.

Comment thread internal/vfs/osvfs/eintr_unix.go Outdated
Comment thread internal/vfs/osvfs/realpath_darwin.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants