Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

On Linux, is access() faster than stat()?

I would have assumed that access() was just a wrapper around stat(), but I've been googling around and have found some anecdotes about replacing stat calls with 'cheaper' access calls. Assuming you are only interested in checking if a file exists, is access faster? Does it completely vary by filesystem?

like image 395
Joseph Garvin Avatar asked Sep 23 '15 20:09

Joseph Garvin


1 Answers

Theory

I doubt that.

In lower layers of kernel there is no much difference between access() and stat() calls both are performing lookup operation: they map file name to an entry in dentry cache and to inode (it is actual kernel structure, inode). Lookup is slow operation because you need to perform it for each part of path, i.e. for /usr/bin/cat you will need to lookup usr, bin and then cat and it can require reading from disk -- that is why inodes and dentries are cached in memory.

Major difference between that calls is that stat() performs conversion of inode structure to stat structure, while access() will do a simple check, but that time is small comparing with lookup time.

The real performance gain can be achieved with at-operations like faccessat() and fstatat(), which allow to open() directory once, just compare:

struct stat s;
stat("/usr/bin/cat", &s);   // lookups usr, bin and cat = 3
stat("/usr/bin/less", &s);  // lookups usr, bin and less = 3

int fd = open("/usr/bin");  // lookups usr, bin = 2
fstatat(fd, "cat", &s);     // lookups cat = 1
fstatat(fd, "less", &s);    // lookups less = 1

Experiments

I wrote a small python script which calls stat() and access():

import os, time, random
files = ['gzexe', 'catchsegv', 'gtroff', 'gencat', 'neqn', 'gzip', 
        'getent', 'sdiff', 'zcat', 'iconv', 'not_exists', 'ldd', 
        'unxz', 'zcmp', 'locale', 'xz', 'zdiff', 'localedef', 'xzcat']
access = lambda fn: os.access(fn, os.R_OK)

for i in xrange(1, 80000): 
    try:
        random.choice((access, os.stat))("/usr/bin/" + random.choice(files))
    except:
        continue

I traced system with SystemTap to measure time spent in different operations. Both stat() and access() system calls use user_path_at_empty() kernel function which represents lookup operation:

stap -ve ' global tm, times, path;
probe lookup = kernel.function("user_path_at_empty") 
    { name = "lookup"; pathname = user_string_quoted($name); }
probe lookup.return = kernel.function("user_path_at_empty").return 
    { name = "lookup"; }
probe stat = syscall.stat 
    { pathname = filename; }
probe stat, syscall.access, lookup
        { if(pid() == target() && isinstr(pathname, "/usr/bin")) {
        tm[name] = local_clock_ns(); } }
probe syscall.stat.return, syscall.access.return, lookup.return
        { if(pid() == target() && tm[name]) {
        times[name] <<< local_clock_ns() - tm[name];
        delete tm[name];
        } }
    ' -c 'python stat-access.py'

Here are the results:

         COUNT      AVG
lookup   80018    1.67 us
stat     40106    3.92 us
access   39903    4.27 us

Note that I disabled SELinux in my experiments, as it adds significant influence on the results.

like image 62
myaut Avatar answered Oct 17 '22 11:10

myaut