Достаточно быстрый способ прохождения дерева каталогов в Python? - PullRequest
3 голосов
/ 23 апреля 2010

Предполагая, что данное дерево каталогов имеет разумный размер: скажем, проект с открытым исходным кодом, такой как Twisted или Python, какой самый быстрый способ пройти и перебрать абсолютный путь всех файлов / каталогов внутри этого каталога?

Я хочу сделать это из Python. os.path.walk медленно. Поэтому я попробовал ls -lR и tree -fi . Для проекта с 8337 файлами (включая файлы tmp, pyc, test, .svn):

$ time tree -fi > /dev/null 

real    0m0.170s
user    0m0.044s
sys     0m0.123s

$ time ls -lR > /dev/null 

real    0m0.292s
user    0m0.138s
sys     0m0.152s

$ time find . > /dev/null 

real    0m0.074s
user    0m0.017s
sys     0m0.056s
$

tree представляется быстрее, чем ls -lR (хотя ls -R быстрее, чем tree, но он не дает полных путей). find самый быстрый.

Кто-нибудь может придумать более быстрый и / или лучший подход? В Windows я могу просто отправить 32-битный двоичный файл tree.exe или ls.exe, если необходимо.

Обновление 1 : добавлено find

Обновление 2 : Почему я хочу это сделать? ... Я пытаюсь сделать разумную замену для команд cd, pushd и т. Д. И команд-оболочек для других команд, использующих проходящие пути (less, more, cat, vim, tail). Для этого программа иногда использует обход файлов (например, если набрать «cd sr grai pat lxml», это автоматически переведет в «cd src / pypm / grail / patches / lxml»). Я не буду удовлетворен, если эта замена компакт-диска займет, скажем, полсекунды для запуска. См http://github.com/srid/pf

Ответы [ 4 ]

3 голосов
/ 23 апреля 2010

Ваш подход в pf будет безнадежно медленным, даже если os.path.walk совсем не занял время.Выполнение соответствия регулярному выражению, содержащее 3 неограниченных замыкания по всем существующим путям, сразу же убьет вас.Вот код из Кернигана и Пайка , на который я ссылался, это правильный алгоритм для задачи:

/* spname:  return correctly spelled filename */
/*
 * spname(oldname, newname)  char *oldname, *newname;
 *  returns -1 if no reasonable match to oldname,
 *           0 if exact match,
 *           1 if corrected.
 *  stores corrected name in newname.
 */

#include <sys/types.h>
#include <sys/dir.h>

spname(oldname, newname)
    char *oldname, *newname;
{
    char *p, guess[DIRSIZ+1], best[DIRSIZ+1];
    char *new = newname, *old = oldname;

    for (;;) {
        while (*old == '/') /* skip slashes */
            *new++ = *old++;
        *new = '\0';
        if (*old == '\0')   /* exact or corrected */
            return strcmp(oldname,newname) != 0;
        p = guess;  /* copy next component into guess */
        for ( ; *old != '/' && *old != '\0'; old++)
            if (p < guess+DIRSIZ)
                *p++ = *old;
        *p = '\0';
        if (mindist(newname, guess, best) >= 3)
            return -1;  /* hopeless */
        for (p = best; *new = *p++; ) /* add to end */
            new++;                    /* of newname */
    }
}

mindist(dir, guess, best)   /* search dir for guess */
    char *dir, *guess, *best;
{
    /* set best, return distance 0..3 */
    int d, nd, fd;
    struct {
        ino_t ino;
        char  name[DIRSIZ+1];   /* 1 more than in dir.h */
    } nbuf;

    nbuf.name[DIRSIZ] = '\0';   /* +1 for terminal '\0' */
    if (dir[0] == '\0')     /* current directory */
        dir = ".";
    d = 3;  /* minimum distance */
    if ((fd=open(dir, 0)) == -1)
        return d;
    while (read(fd,(char *) &nbuf,sizeof(struct direct)) > 0)
        if (nbuf.ino) {
            nd = spdist(nbuf.name, guess);
            if (nd <= d && nd != 3) {
                strcpy(best, nbuf.name);
                d = nd;
                if (d == 0)     /* exact match */
                    break;
            }
        }
    close(fd);
    return d;
}

/* spdist:  return distance between two names */
/*
 *  very rough spelling metric:
 *  0 if the strings are identical
 *  1 if two chars are transposed
 *  2 if one char wrong, added or deleted
 *  3 otherwise
 */

#define EQ(s,t) (strcmp(s,t) == 0)

spdist(s, t)
    char *s, *t;
{
    while (*s++ == *t)
        if (*t++ == '\0')
            return 0;       /* exact match */
    if (*--s) {
        if (*t) {
            if (s[1] && t[1] && *s == t[1] 
              && *t == s[1] && EQ(s+2, t+2))
                return 1;   /* transposition */
            if (EQ(s+1, t+1))
                return 2;   /* 1 char mismatch */
        }
        if (EQ(s+1, t))
            return 2;       /* extra character */
    }
    if (*t && EQ(s, t+1))
        return 2;           /* missing character */
    return 3;
}

Примечание : этот код был написан задолго доANSI C, ISO C или POSIX, что угодно, даже можно было представить, когда один читал файлы каталога в сыром виде.Подход кода гораздо более полезен, чем всякое перемещение указателя.

1 голос
/ 23 апреля 2010

Было бы трудно добиться гораздо лучшего, чем find по производительности, но вопрос в том, насколько быстрее и зачем он нужен так быстро? Вы утверждаете, что os.path.walk медленный, действительно, он примерно в 3 раза медленнее на моей машине по дереву из 16 тыс. Каталогов. Но опять же, мы говорим о разнице между 0,68 секундами и 1,9 секундами для Python.

Если ваша цель - установить рекорд скорости, вы не можете побить жестко закодированный C, который полностью на 75% связан с системным вызовом, и вы не можете заставить ОС работать быстрее. Тем не менее, 25% времени Python тратится на системные вызовы. Что вы хотите делать с пройденными путями?

0 голосов
/ 21 марта 2012

Хотя я сомневаюсь, что у вас есть несколько головок для чтения, вот как вы можете пройти несколько миллионов файлов (мы сделали 10M + за несколько минут).

https://github.com/hpc/purger/blob/master/src/treewalk/treewalk.c

0 голосов
/ 23 апреля 2010

Одним из решений, которое вы не упомянули, является os.walk. Я не уверен, что это будет быстрее, чем os.path.walk, но объективно лучше.

Вы еще не сказали, что собираетесь делать со списком каталогов, когда он у вас есть, поэтому трудно дать более конкретные предложения.

...