Skip to content

perf: direct syscall fastpath for absolute paths in zend_virtual_cwd#22722

Open
henderkes wants to merge 2 commits into
php:masterfrom
henderkes:perf/vcwd-direct
Open

perf: direct syscall fastpath for absolute paths in zend_virtual_cwd#22722
henderkes wants to merge 2 commits into
php:masterfrom
henderkes:perf/vcwd-direct

Conversation

@henderkes

@henderkes henderkes commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Absolute paths don't need to go through the per-thread cwd emulation, add a fast-path to directly hand them to libc.

since relative paths won't start with a leading slash, this adds negligible cost for them

<?php
chdir(__DIR__);

$s = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    file_exists("data.txt");
}
$rel = microtime(true) - $s;

$abs = __DIR__ . "/data.txt";
$s = microtime(true);
for ($i = 0; $i < 1000000; $i++) {
    file_exists($abs);
}
$abs_t = microtime(true) - $s;
workload master patch delta
relative path 0.3521s 0.3524s +0.09%
absolute path 0.3162s 0.2623s −17.05%

Benchmark for Symfony demo:

compilerroutemastervcwd-directdelta
gcc 15/en (home)2741.82868.2+4.61%
gcc 15/en/blog/1038.81066.5+2.67%
clang 21/en (home)2740.72876.8+4.97%
clang 21/en/blog/1031.41065.8+3.34%

cc @dunglas because this will be a nice improvement for frankenphp (though fpm alike) in 8.6 :)

Comment thread Zend/zend_virtual_cwd.c

#ifndef ZEND_WIN32
if (virtual_path_is_direct(path)) {
return fopen(path, mode);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

should this use php_win32_ioutil_fopen on windows?

@staabm staabm Jul 14, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ohh its ifNdef ZEND_WIN32 .. nevermind

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah, I will revisit all these small findings later on Windows

@henderkes

Copy link
Copy Markdown
Contributor Author

Just realised that this doesn't apply to dirty composer autoloader paths with ../vendor/*. Is there any site resolving a path lexically instead of physically? If not I could drop the dot checks.

@henderkes henderkes left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The only thing I'm not entirely sure is whether we couldn't switch to using fstatat and siblings instead of doing the manual string operations for every path operation that doesn't need the resolved string.

Most importantly, ZTS (/a/c) differs from what NTS is doing (/x/c) when it could be using direct syscalls, get NTS' performance and also resolve (correctly) even for relative paths.

Comment thread Zend/zend_virtual_cwd.c
#endif

CWD_STATE_COPY(&new_state, &CWDG(cwd));
if (virtual_file_ex(&new_state, path, NULL, CWD_EXPAND)) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

just realized github doesn't display these lines by default in the diff - the use_realpath parameter to virtual_file_ex is passed through to tsrm_realpath_r which does:

		save = (use_realpath != CWD_EXPAND); // line 573

which disables per-component lstat/readlink. So it strips .. segments lexically and may get a different path.

Example:

/a/b/../c
/a/b is a symlink to /x
lstat/readlink resolve to /x/c
virtual_lstat resolves to /a/c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants