2016-03-06 3 views
2

Я пишу клон 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 и getpwuidshould not be freed, для наших функций обертки нет необходимости требовать free.

+0

Эта версия с 'getgrgid()' определенно проще, чем другая. Вы проверяли, получает ли тот же механизм в 'do_get_user' что-нибудь? Также вы должны сбросить кеш с помощью 'cache_gid = -1;', если вы вызываете 'getgrgid()', потому что вы не знаете, является ли 'cache_gr_name' еще действительным, если' getgrgid' терпит неудачу. – chqrlie

+0

@chqrlie: Совершенно то же самое относится к 'getpwuid' /' do_get_user'. Сначала я подумал, что было бы лишним вставить код для него, но я добавил его сейчас. Я не думаю, что очистка кеша необходима, потому что даже если предыдущий запуск getgrgid не прошел, «cache_gid» не будет затронут. – amq

+0

Я попытался умышленно сделать 'getgrgid' fail и установить' grp' в 'NULL' случайным образом или если' gid'> 10, и все работало, как ожидалось. Даже установка 'grp = NULL;' прямо перед 'return strdup (cache_gr_name);' ничего не сломает. Может ли «getgrgid» повлиять на 'cache_gr_name', считая, что пользователь освобожден в' do_ls'? – amq

ответ

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); 
} 
+0

Чтобы продемонстрировать это, попробуйте запустить команды в обратном порядке. –

+0

Замедление повторяемо, я могу запускать обе команды 10 раз в любом порядке, и всегда будет разница в 3 раза. – amq

+0

OP использует 'getgrgid_r', который равен' getgrgid', за исключением того, что он не хранит данные в статическом буфере и, следовательно, является потокобезопасным. – fuz

0

getpwuid То ли и getgrgid вызовы кэшированных зависит от того, как они реализуются, и от того, как настроена система. Недавно я написал реализацию ls и столкнулся с аналогичной проблемой.

Я обнаружил, что на всех современных системах, которые я тестировал, две функции не удалены, если вы не запустили кеширование имен службы dæmon (ncsd), и в этом случае ncsd убедится, что кеш остается актуальным. Легко понять, почему это происходит: без ncsd кеширование информации может привести к устаревшему выходу, что является нарушением specification.

Я не думаю, что вы должны полагаться на эти функции, кэшируя базы данных групп и passwd, потому что они часто этого не делают. Для этой цели я внедрил для этого специальный код кеширования. Если вам не требуется иметь актуальную информацию в случае изменения содержимого базы данных во время выполнения программы, это прекрасно.

Вы можете найти мою реализацию такого кеша here. Я не собираюсь публиковать его в Stack Overflow, так как я не хочу публиковать код под лицензией MIT.

Смежные вопросы