Команда ls не працює для каталогу з величезною кількістю файлів


70

У мене був каталог, який мав близько 5 мільйонів файлів. Коли я намагався запустити lsкоманду всередині цього каталогу, моя система спожила величезну кількість пам’яті, і вона десь зависла. Чи є ефективний спосіб перерахувати файли, крім використання lsкоманди?


11
Переконайтеся, що у вас немає псевдоніма для lsцього використання, --colorабо -Fщо це означатиме створення lstat(2)файлу для кожного файла.
Стефан Шазелас

4
До речі, зберігання мільйонів файлів в одному каталозі - досить погана ідея. Якщо ви керуєте компонуванням каталогу, можливо, розділите його за якимись критеріями?
d33tah

Це був чистий lsдзвінок чи ви використовували варіанти?
Hauke ​​Laging

1
@ d33tah Так, 5 мільйонів - це багато! Моя коренева файлова система має обмеження в 7 мільйонів входів.
Мікель

7
5 мільйонів предметів для виведення - як ви дивитесь на це - простий лістинг - це занадто багато, щоб побачити - так для чого вам потрібен перелік?
користувач151019

Відповіді:


66

Уникайте сортування за допомогою:

ls --sort=none # "do not sort; list entries in directory order"

Або, рівнозначно:

ls -U

10
Цікаво, скільки також додає накладні розкладки стовпців. Додавання -1прапора може допомогти.
Мікель

Напевно, не багато, але кожен трошки допомагає, правда? :)
Мікель

1
@Mikel Це лише здогадка, чи ти це виміряв? Мені здається, це -1займає ще більше часу.
Hauke ​​Laging

10
"-1" допомагає зовсім небагато. "ls -f -1" уникне будь-яких статистичних дзвінків і надрукує все негайно. Вихід стовпця (який за замовчуванням при відправці до терміналу) робить його спочатку буферизованим. У моїй системі, використовуючи btrfs у каталозі з 8 мільйонами файлів (як створено "seq 1 8000000 | xargs touch"), "час ls -f -1 | wc -l" займає менше 5 секунд, а "time ls -f -C | wc -l "займає більше 30 секунд.
Скотт Ламб

1
@ToolmakerSteve Поведінка за замовчуванням ( -Cколи stdout є терміналом, -1коли це труба) заплутано. Коли ви експериментуєте і вимірюєте, ви переходите між переглядом результату (щоб переконатися, що команда виконує те, що ви очікуєте), і придушуючи його (щоб уникнути заплутаного фактора пропускної здатності термінальної програми). Краще використовувати команди , які ведуть себе таким же чином в обох режимах, так явно визначити формат виведення через -1, -C, -lі т.д.
Скотт Lamb

47

lsнасправді сортує файли та намагається перелічити їх, що стає величезною накладною витратою, якщо ми намагаємось перелічити понад мільйон файлів всередині каталогу. Як згадується в цьому посиланні, ми можемо використовувати straceабо findперераховувати файли. Однак ці варіанти також видалися нездійсненними для моєї проблеми, оскільки у мене було 5 мільйонів файлів. Через якийсь - то трохи Googling, я виявив , що якщо список каталогів з допомогою getdents(), він повинен бути швидше, тому що ls, findі Pythonбібліотеки використовують readdir()яка повільніше , але використовує getdents()внизу.

Ми можемо знайти код C для перегляду списку файлів , використовуючи getdents()від сюди :

/*
 * List directories using getdents() because ls, find and Python libraries
 * use readdir() which is slower (but uses getdents() underneath.
 *
 * Compile with 
 * ]$ gcc  getdents.c -o getdents
 */
#define _GNU_SOURCE
#include <dirent.h>     /* Defines DT_* constants */
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/syscall.h>

#define handle_error(msg) \
       do { perror(msg); exit(EXIT_FAILURE); } while (0)

struct linux_dirent {
   long           d_ino;
   off_t          d_off;
   unsigned short d_reclen;
   char           d_name[];
};

#define BUF_SIZE 1024*1024*5

int
main(int argc, char *argv[])
{
   int fd, nread;
   char buf[BUF_SIZE];
   struct linux_dirent *d;
   int bpos;
   char d_type;

   fd = open(argc > 1 ? argv[1] : ".", O_RDONLY | O_DIRECTORY);
   if (fd == -1)
       handle_error("open");

   for ( ; ; ) {
       nread = syscall(SYS_getdents, fd, buf, BUF_SIZE);
       if (nread == -1)
           handle_error("getdents");

       if (nread == 0)
           break;

       for (bpos = 0; bpos < nread;) {
           d = (struct linux_dirent *) (buf + bpos);
           d_type = *(buf + bpos + d->d_reclen - 1);
           if( d->d_ino != 0 && d_type == DT_REG ) {
              printf("%s\n", (char *)d->d_name );
           }
           bpos += d->d_reclen;
       }
   }

   exit(EXIT_SUCCESS);
}

Скопіюйте програму C вище в каталог, в якому потрібно вказати файли. Потім виконайте наведені нижче команди.

gcc  getdents.c -o getdents
./getdents

Приклад хронометражу : getdentsможе бути набагато швидшим, ніж ls -fзалежно від конфігурації системи. Ось кілька таймінгів, що демонструють 40-кратну швидкість для переліку каталогу, що містить близько 500k файлів через кріплення NFS в обчислювальному кластері. Кожна команда виконувалась 10 разів у першу чергу getdents, спочатку , потім ls -f. Перший запуск значно повільніше, ніж усі інші, ймовірно, через помилки кешування сторінки NFS. (Убік: над цим монтом d_typeполе є ненадійним, у тому сенсі, що багато файлів видаються як "невідомий" тип.)

command: getdents $bigdir
usr:0.08 sys:0.96  wall:280.79 CPU:0%
usr:0.06 sys:0.18  wall:0.25   CPU:97%
usr:0.05 sys:0.16  wall:0.21   CPU:99%
usr:0.04 sys:0.18  wall:0.23   CPU:98%
usr:0.05 sys:0.20  wall:0.26   CPU:99%
usr:0.04 sys:0.18  wall:0.22   CPU:99%
usr:0.04 sys:0.17  wall:0.22   CPU:99%
usr:0.04 sys:0.20  wall:0.25   CPU:99%
usr:0.06 sys:0.18  wall:0.25   CPU:98%
usr:0.06 sys:0.18  wall:0.25   CPU:98%
command: /bin/ls -f $bigdir
usr:0.53 sys:8.39  wall:8.97   CPU:99%
usr:0.53 sys:7.65  wall:8.20   CPU:99%
usr:0.44 sys:7.91  wall:8.36   CPU:99%
usr:0.50 sys:8.00  wall:8.51   CPU:100%
usr:0.41 sys:7.73  wall:8.15   CPU:99%
usr:0.47 sys:8.84  wall:9.32   CPU:99%
usr:0.57 sys:9.78  wall:10.36  CPU:99%
usr:0.53 sys:10.75 wall:11.29  CPU:99%
usr:0.46 sys:8.76  wall:9.25   CPU:99%
usr:0.50 sys:8.58  wall:9.13   CPU:99%

14
Чи можете ви додати невеликий орієнтир у строках, для яких відображається ваш випадок ls?
Бернхард

1
Солодке. І ви можете додати можливість просто рахувати записи (файли), а не перелічувати їх імена (заощаджуючи мільйони дзвінків до printf, для цього списку).
ChuckCottrill

29
Ви знаєте, що ваш каталог занадто великий, коли вам потрібно писати спеціальний код, щоб перерахувати його вміст ...
casey

1
@casey За винятком того, що вам не потрібно. Вся ця розмова про getdentsvs readdirпропускає суть.
Мікель

9
Давай! У ньому вже є 5 мільйонів файлів. Помістіть власну програму "ls" в інший каталог.
Йохан

12

Найбільш вірогідною причиною, чому це повільно, є фарбування типу файлу, ви можете уникнути цього за допомогою \lsабо /bin/lsвимкнути параметри кольорів.

Якщо у вас дійсно так багато файлів у режимі, використання findнатомість - також хороший варіант.


7
Я не думаю, що це повинно було бути знято. Сортування є однією проблемою, але навіть без сортування ls -U --colorце займе тривалий час, оскільки це зробить statкожен файл. Тож обидва вірні.
Мікель

Вимкнення забарвлення має величезний вплив на продуктивність, lsі воно за замовчуванням відстежується у багатьох багатьох .bashrcз них.
Віктор Шредер

Так, я зробив /bin/ls -Uі отримав вихід за
короткий

-3

Я вважаю, що echo *працює набагато швидше, ніж ls. YMMV.


4
Оболонка буде сортувати *. Таким чином, цей шлях, ймовірно, дуже повільний для 5 мільйонів файлів.
Мікель

3
@Mikel Більше того, я майже впевнений, що 5 мільйонів файлів закінчуються точкою, коли глобус повністю розіб'ється.
злий

4
Мінімальна довжина імені файлу (для 5 мільйонів файлів) - це 3 символи (можливо 4, якщо ви дотримуєтесь більш поширених символів) плюс розділові знаки = 4 символи на файл, тобто 20 Мб аргументів команди. Це набагато більше загальної довжини командного рядка на 2 Мб. Exec (і навіть вбудовані) заграв би.
Йохан
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.