getpwuid_r和getgrgid_r很慢,如何缓存它们?

时间:2016-03-06 00:26:28

标签: c data-structures

我在学习C时正在编写find的副本。在实施-ls选项时,我偶然发现了getpwuid_rgetgrgid_r来电确实存在的问题慢,这同样适用于getpwuidgetgrgid。我需要它们显示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 - usernamegid - 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。由于getgrgidgetpwuid should not be freed的输出,我们的包装函数不需要free

2 个答案:

答案 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)

是否缓存getpwuidgetgrgid调用取决于它们的实现方式以及系统的配置方式。我最近编写了ls的实现并遇到了类似的问题。

我发现在我测试的所有现代系统中,除非你运行名称服务缓存守护进程(nscd),否则这两个函数都是未缓存的,在这种情况下,nscd确保缓存保持最新。很容易理解为什么会发生这种情况:如果没有nscd,缓存信息可能会导致过时的输出,这违反了specification

我认为您不应该依赖这些函数来缓存组和passwd数据库,因为它们通常不会。我为此目的实现了自定义缓存代码。如果在程序执行期间数据库内容发生变化,您不需要获取最新信息,这样就可以了。

您可以找到我对此类缓存here的实现。我不打算在Stack Overflow上发布它,因为我不希望在MIT许可下发布代码。