我在学习C时正在编写find
的副本。在实施-ls
选项时,我偶然发现了getpwuid_r
和getgrgid_r
来电确实存在的问题慢,这同样适用于getpwuid
和getgrgid
。我需要它们显示stat.h
提供的ID中的用户/组名称。
例如,列出整个文件系统的速度要慢3倍:
# 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。
更新
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;
}
更新2:
将long
更改为unsigned int
。
更新3:
添加了缓存清除。这是绝对必要的,因为pwd->pw_name
可能指向静态区域。如果{1}}失败,或者只是在程序中的其他位置执行,getpwuid
可以覆盖其内容。
同时删除strdup
。由于getgrgid
和getpwuid
should not be freed的输出,我们的包装函数不需要free
。
答案 0 :(得分:1)
时间确实表明对这些功能的强烈怀疑。
查看您的函数do_get_group
,存在一些问题:
您对sysconf(_SC_GETPW_R_SIZE_MAX);
和do_get_group
的每次通话都使用do_get_user
,绝对缓存,在程序的生命周期内不会更改,但您不会获得太多收益
您使用attr.st_uid
而不是attr.st_gid
,这可能导致许多文件的查找失败,可能会破坏缓存机制(如果有的话)。首先解决这个问题,这是一个错误!
您可以返回调用者不应传递给free()
的值,例如grp->gr_name
和""
。您应该始终分配您返回的字符串。 do_get_user()
中可能存在同样的问题。
以下是具有一次性缓存的do_get_group
的替代。看看这是否会提高性能:
/*
* @brief returns the groupname or gid, if group not present on the system
*
* @param attr the entry attributes from lstat
*
* @returns the groupname if getgrgid() worked, otherwise gid, as a string
*/
char *do_get_group(struct stat attr) {
char *group;
struct group grp;
struct group *result;
static size_t length = 0;
static char *buffer = NULL;
static gid_t cache_gid = -1;
static char *cache_gr_name = NULL;
if (!length) {
/* only allocate the buffer once */
long sysconf_length = sysconf(_SC_GETPW_R_SIZE_MAX);
if (sysconf_length == -1) {
sysconf_length = 16384;
}
length = (size_t)sysconf_length;
buffer = calloc(length, 1);
}
if (!buffer) {
fprintf(stderr, "%s: malloc(): %s\n", program, strerror(errno));
return strdup("");
}
/* check the cache */
if (cache_gid == attr.st_gid) {
return strdup(cache_gr_name);
}
/* empty the cache */
cache_gid = -1;
free(cache_gr_name);
cache_gr_name = NULL;
if (getgrgid_r(attr.st_gid, &grp, buffer, length, &result) != 0) {
fprintf(stderr, "%s: getpwuid_r(): %s\n", program, strerror(errno));
return strdup("");
}
if (result) {
group = grp.gr_name;
} else {
group = buffer;
if (snprintf(group, length, "%ld", (long)attr.st_gid) < 0) {
fprintf(stderr, "%s: snprintf(): %s\n", program, strerror(errno));
return strdup("");
}
}
/* load the cache */
cache_gid = attr.st_gid;
cache_gr_name = strdup(group);
return strdup(group);
}
答案 1 :(得分:1)
是否缓存getpwuid
和getgrgid
调用取决于它们的实现方式以及系统的配置方式。我最近编写了ls
的实现并遇到了类似的问题。
我发现在我测试的所有现代系统中,除非你运行名称服务缓存守护进程(nscd),否则这两个函数都是未缓存的,在这种情况下,nscd确保缓存保持最新。很容易理解为什么会发生这种情况:如果没有nscd,缓存信息可能会导致过时的输出,这违反了specification。
我认为您不应该依赖这些函数来缓存组和passwd数据库,因为它们通常不会。我为此目的实现了自定义缓存代码。如果在程序执行期间数据库内容发生变化,您不需要获取最新信息,这样就可以了。
您可以找到我对此类缓存here的实现。我不打算在Stack Overflow上发布它,因为我不希望在MIT许可下发布代码。