Skip to content

Commit 9013c51

Browse files
committed
vfs: mostly undo glibc turning 'fstat()' into 'fstatat(AT_EMPTY_PATH)'
Mateusz reports that glibc turns 'fstat()' calls into 'fstatat()', and that seems to have been going on for quite a long time due to glibc having tried to simplify its stat logic into just one point. This turns out to cause completely unnecessary overhead, where we then go off and allocate the kernel side pathname, and actually look up the empty path. Sure, our path lookup is quite optimized, but it still causes a fair bit of allocation overhead and a couple of completely unnecessary rounds of lockref accesses etc. This is all hopefully getting fixed in user space, and there is a patch floating around for just having glibc use the native fstat() system call. But even with the current situation we can at least improve on things by catching the situation and short-circuiting it. Note that this is still measurably slower than just a plain 'fstat()', since just checking that the filename is actually empty is somewhat expensive due to inevitable user space access overhead from the kernel (ie verifying pointers, and SMAP on x86). But it's still quite a bit faster than actually looking up the path for real. To quote numers from Mateusz: "Sapphire Rapids, will-it-scale, ops/s stock fstat 5088199 patched fstat 7625244 (+49%) real fstat 8540383 (+67% / +12%)" where that 'stock fstat' is the glibc translation of fstat into fstatat() with an empty path, the 'patched fstat' is with this short circuiting of the path lookup, and the 'real fstat' is the actual native fstat() system call with none of this overhead. Link: https://lore.kernel.org/lkml/20230903204858.lv7i3kqvw6eamhgz@f/ Reported-by: Mateusz Guzik <mjguzik@gmail.com> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
1 parent 7ba2090 commit 9013c51

File tree

1 file changed

+17
-0
lines changed

1 file changed

+17
-0
lines changed

fs/stat.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,23 @@ int vfs_fstatat(int dfd, const char __user *filename,
311311
int statx_flags = flags | AT_NO_AUTOMOUNT;
312312
struct filename *name;
313313

314+
/*
315+
* Work around glibc turning fstat() into fstatat(AT_EMPTY_PATH)
316+
*
317+
* If AT_EMPTY_PATH is set, we expect the common case to be that
318+
* empty path, and avoid doing all the extra pathname work.
319+
*/
320+
if (dfd >= 0 && flags == AT_EMPTY_PATH) {
321+
char c;
322+
323+
ret = get_user(c, filename);
324+
if (unlikely(ret))
325+
return ret;
326+
327+
if (likely(!c))
328+
return vfs_fstat(dfd, stat);
329+
}
330+
314331
name = getname_flags(filename, getname_statx_lookup_flags(statx_flags), NULL);
315332
ret = vfs_statx(dfd, name, statx_flags, stat, STATX_BASIC_STATS);
316333
putname(name);

0 commit comments

Comments
 (0)