Я пишу клон find
учась C. При осуществлении -ls
варианта я наткнулся на проблему, что getpwuid_r
и getgrgid_r
вызовов очень медленно, то же самое относится и к getpwuid
и getgrgid
. Мне нужно, чтобы они отображали имена пользователей/групп из идентификаторов, предоставленные stat.h
.getpwuid_r и getgrgid_r медленны, как я могу их кэшировать?
Например, перечисляя всю файловую систему получает 3x медленнее:
# measurements were made 3 times and the fastest run was recorded
# with getgrgid_r
time ./myfind/-ls > list.txt
real 0m4.618s
user 0m1.848s
sys 0m2.744s
# getgrgid_r replaced with 'return "user";'
time ./myfind/-ls > list.txt
real 0m1.437s
user 0m0.572s
sys 0m0.832s
Интересно, как GNU находка поддерживает такую хорошую скорость. Я видел sources, но они не совсем легко понять и применять без специальных типов, макросы и т.д.:
time find/-ls > list.txt
real 0m1.544s
user 0m0.884s
sys 0m0.648s
Я думал о кэшировании uid - username
и gid - groupname
пара в структуре данных. Это хорошая идея? Как вы его реализуете?
Вы можете найти мой полный код here.
UPDATE:
solution было именно то, что я искал:
time ./myfind/-ls > list.txt
real 0m1.480s
user 0m0.696s
sys 0m0.736s
Вот версия, основанная на getgrgid
(если не требуется безопасность потока):
char *do_get_group(struct stat attr) {
struct group *grp;
static unsigned int cache_gid = UINT_MAX;
static char *cache_gr_name = NULL;
/* skip getgrgid if we have the record in cache */
if (cache_gid == attr.st_gid) {
return cache_gr_name;
}
/* clear the cache */
cache_gid = UINT_MAX;
grp = getgrgid(attr.st_gid);
if (!grp) {
/*
* the group is not found or getgrgid failed,
* return the gid as a string then;
* an unsigned int needs 10 chars
*/
char group[11];
if (snprintf(group, 11, "%u", attr.st_gid) < 0) {
fprintf(stderr, "%s: snprintf(): %s\n", program, strerror(errno));
return "";
}
return group;
}
cache_gid = grp->gr_gid;
cache_gr_name = grp->gr_name;
return grp->gr_name;
}
getpwuid
:
char *do_get_user(struct stat attr) {
struct passwd *pwd;
static unsigned int cache_uid = UINT_MAX;
static char *cache_pw_name = NULL;
/* skip getpwuid if we have the record in cache */
if (cache_uid == attr.st_uid) {
return cache_pw_name;
}
/* clear the cache */
cache_uid = UINT_MAX;
pwd = getpwuid(attr.st_uid);
if (!pwd) {
/*
* the user is not found or getpwuid failed,
* return the uid as a string then;
* an unsigned int needs 10 chars
*/
char user[11];
if (snprintf(user, 11, "%u", attr.st_uid) < 0) {
fprintf(stderr, "%s: snprintf(): %s\n", program, strerror(errno));
return "";
}
return user;
}
cache_uid = pwd->pw_uid;
cache_pw_name = pwd->pw_name;
return pwd->pw_name;
}
UPDATE 2:
Изменено long
к unsigned int
.
UPDATE 3:
Добавлена очистка кэша. Это абсолютно необходимо, потому что pwd->pw_name
может указывать на статическую область. getpwuid
может перезаписать его содержимое, если он не работает или просто выполняется в другом месте программы.
Также удалено strdup
. Поскольку выходные данные getgrgid
и getpwuid
should not be freed, для наших функций обертки нет необходимости требовать free
.
Эта версия с 'getgrgid()' определенно проще, чем другая. Вы проверяли, получает ли тот же механизм в 'do_get_user' что-нибудь? Также вы должны сбросить кеш с помощью 'cache_gid = -1;', если вы вызываете 'getgrgid()', потому что вы не знаете, является ли 'cache_gr_name' еще действительным, если' getgrgid' терпит неудачу. – chqrlie
@chqrlie: Совершенно то же самое относится к 'getpwuid' /' do_get_user'. Сначала я подумал, что было бы лишним вставить код для него, но я добавил его сейчас. Я не думаю, что очистка кеша необходима, потому что даже если предыдущий запуск getgrgid не прошел, «cache_gid» не будет затронут. – amq
Я попытался умышленно сделать 'getgrgid' fail и установить' grp' в 'NULL' случайным образом или если' gid'> 10, и все работало, как ожидалось. Даже установка 'grp = NULL;' прямо перед 'return strdup (cache_gr_name);' ничего не сломает. Может ли «getgrgid» повлиять на 'cache_gr_name', считая, что пользователь освобожден в' do_ls'? – amq