Команда 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.