В Linux работает ли access() быстрее, чем stat()?

Я бы предположил, что access() был просто оболочкой для stat(), но я погуглил и нашел нашел несколько анекдотов о замене вызовов статистики "более дешевыми" вызовами доступа. Предполагая, что вас интересует только проверка существования файла, быстрее ли доступ? Это полностью зависит от файловой системы?


person Joseph Garvin    schedule 23.09.2015    source источник
comment
это похоже на обертку open(). для меня имеет смысл, что это будет быстрее, и приятно, что он осведомлен о настройках. Я хотел бы увидеть некоторые тесты, хотя.   -  person Matt Joyce    schedule 23.09.2015
comment
access находится в разделе man 2, поэтому я предполагаю, что это собственный системный вызов, а не оболочка libc для чего-то (так что теоретически это может быть быстрее, чем stat и т. д.)   -  person Colonel Thirty Two    schedule 24.09.2015
comment
Важна не производительность, а семантика. access() говорит вам, что вы можете читать/записывать/выполнять что-то, даже после того, как были задействованы какие-либо плагины контроля доступа (например, SELinux) или другие компоненты (расширенные ACL, разрешения AFS и т. д.). stat() говорит вам о традиционных правах доступа к файлам UNIX, но они могут быть или не быть всей историей. И поскольку access() [потенциально] выполняет больше работы, я действительно ожидаю, что он будет медленнее.   -  person Charles Duffy    schedule 24.09.2015
comment
Я полагаю, что это очень зависит. Связанный пример: на моем ПК fstat() в 2 раза быстрее, чем pread(). Однако на одном компьютере с малым объемом оперативной памяти и процессором MIPS fstat() в 1,5 раза медленнее, чем pread(). Таким образом, сообщение этого комментария заключается в том, что вы всегда должны измерять, чтобы получить конкретное время для вашей среды.   -  person gavv    schedule 24.09.2015


Ответы (1)


Теория

Сомневаюсь.

На нижних уровнях ядра нет большой разницы между вызовами access() и stat(), оба из которых выполняют операцию поиска: они сопоставляют имя файла с записью в кэше dentry и с inode (это фактическая структура ядра, inode). Поиск — медленная операция, потому что вам нужно выполнять его для каждой части пути, то есть для /usr/bin/cat вам нужно будет искать usr, bin, а затем cat, и это может потребовать чтения с диска — поэтому иноды и дентри кэшируются в памяти.

Основное различие между этими вызовами заключается в том, что stat() выполняет преобразование структуры inode в структуру stat, а access() выполняет простую проверку, но это время мало по сравнению со временем поиска.

Реального прироста производительности можно добиться с помощью операций at, таких как faccessat() и fstatat(), которые позволяют один раз открыть каталог open(), просто сравните:

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

Эксперименты

Я написал небольшой скрипт на Python, который вызывает stat() и 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

Я проследил систему с помощью SystemTap, чтобы измерить время, затрачиваемое на различные операции. Оба системных вызова stat() и access() используют функцию ядра user_path_at_empty(), которая представляет операцию поиска:

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'

Вот результаты:

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

Обратите внимание, что в своих экспериментах я отключил SELinux, так как он сильно влияет на результаты.

person myaut    schedule 23.09.2015
comment
Вы измеряете кешированное дело. Возможно, stat должен проверить некоторые вещи, которые не делает access, например количество блоков. Большинство файловых систем, вероятно, хранят количество блоков в индексном узле, вместо того, чтобы проходить по B-дереву карты блоков, потому что stat-every-file в каталоге — очень распространенная операция, для которой они должны быть оптимизированы. Учитывая это, все, что нужно для ответа на запрос stat, вероятно, исходит из одного поиска на диске для чтения индексного дескриптора. - person Peter Cordes; 24.09.2015
comment
Пожалуйста, не верьте слепо этому совету. Я протестировал его на своей машине, и производительность stat и faccessat кажется полностью эквивалентной. - person Siscia; 12.12.2019
comment
@Siscia, как ты провела свой эксперимент? Без дополнительной информации трудно сказать, какие факторы играют роль. - person myaut; 12.12.2019