Polecenie ls nie działa w przypadku katalogu z dużą liczbą plików

70

Miałem katalog zawierający około 5 milionów plików. Kiedy próbowałem uruchomić lspolecenie z tego katalogu, mój system zużył ogromną ilość pamięci i po pewnym czasie się zawiesił. Czy istnieje skuteczny sposób na wyświetlenie listy plików inny niż użycie lspolecenia?

Ramesh
źródło
11
Upewnij się, że nie mają alias ls, który używa --colorlub -Fjako oznaczałoby to robi lstat(2)dla każdego pliku.
Stéphane Chazelas
4
Nawiasem mówiąc, przechowywanie milionów plików w jednym katalogu jest raczej złym pomysłem. Jeśli kontrolujesz układ katalogu, być może podzielisz go według niektórych kryteriów?
d33tah
Czy to była czysta lsrozmowa, czy korzystałeś z opcji?
Hauke ​​Laging
1
@ d33tah Tak, 5 milionów to dużo! Mój główny system plików ma limit 7 milionów i-węzłów.
Mikel
7
5 milionów pozycji do wydrukowania - jak na to patrzysz - prosta lista to za dużo do zobaczenia - więc po co chcesz aukcji?
user151019,

Odpowiedzi:

66

Unikaj sortowania, używając:

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

Lub równoważnie:

ls -U
Hauke ​​Laging
źródło
10
Zastanawiam się, ile narzut dodaje również układ kolumn. Dodanie -1flagi może pomóc.
Mikel
Prawdopodobnie niewiele, ale każda odrobina pomaga, prawda? :)
Mikel
1
@Mikel Czy to tylko zgadywanie, czy zmierzyłeś to? Wydaje mi się, że -1to trwa jeszcze dłużej.
Hauke ​​Laging
10
„-1” bardzo pomaga. „ls -f -1” pozwoli uniknąć wywołań statystyk i wydrukuje wszystko natychmiast. Dane wyjściowe z kolumny (która jest domyślna przy wysyłaniu do terminala) powodują, że buforuje wszystko najpierw. W moim systemie, używając btrfs w katalogu z 8 milionami plików (utworzonymi przez „seq 1 8000000 | xargs touch”), „czas ls -f -1 | wc -l” zajmuje mniej niż 5 sekund, podczas gdy „czas ls -f -C | wc -l "trwa ponad 30 sekund.
Scott Lamb,
1
@ToolmakerSteve Domyślne zachowanie ( -Cgdy stdout jest terminalem, -1gdy jest potokiem) jest mylące. Podczas eksperymentowania i mierzenia przełączasz się między widokiem wyników (aby upewnić się, że polecenie wykonuje to, czego oczekujesz) a pomijaniem go (aby uniknąć mylącego współczynnika przepustowości aplikacji terminalowej). Lepiej użyć komendy, które zachowują się w ten sam sposób w obu trybach, więc jednoznacznie określić format wyjściowy poprzez -1, -C, -l, itd.
Scott Lamb
47

lsfaktycznie sortuje pliki i próbuje je wyświetlić, co staje się ogromnym narzutem, jeśli próbujemy wyświetlić ponad milion plików w katalogu. Jak wspomniano w tym linku, możemy użyć stracelub findwyświetlić listę plików. Jednak te opcje również wydawały się niewykonalne dla mojego problemu, ponieważ miałem 5 milionów plików. Po trochę googlingu, odkryłem, że jeśli wymienimy listę używanych katalogów getdents(), powinno to być szybsze, ponieważ ls, findi Pythonbiblioteki używają tego, readdir()co jest wolniejsze, ale używa getdents()pod spodem.

Możemy znaleźć kod C, aby wyświetlić listę plików przy użyciu getdents()od tutaj :

/*
 * 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);
}

Skopiuj powyższy program C do katalogu, w którym pliki muszą być wymienione. Następnie wykonaj poniższe polecenia.

gcc  getdents.c -o getdents
./getdents

Przykład taktowania : getdentsmoże być znacznie szybszy niż ls -f, w zależności od konfiguracji systemu. Oto niektóre czasy pokazujące 40-krotny wzrost prędkości dla umieszczenia katalogu zawierającego około 500 tys. Plików przez podłączenie NFS w klastrze obliczeniowym. Każda komenda został uruchomiony 10 razy w bezpośredniej kolejności, najpierw getdents, potem ls -f. Pierwsze uruchomienie jest znacznie wolniejsze niż wszystkie inne, prawdopodobnie z powodu błędów strony buforowania NFS. (Poza tym: w przypadku tego montażu d_typepole jest niewiarygodne, w tym sensie, że wiele plików jest wyświetlanych jako „nieznane”).

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%
Ramesh
źródło
14
Czy możesz dodać mały punkt odniesienia w czasie, dla którego twoja obudowa wyświetla się ls?
Bernhard
1
Słodkie. I możesz dodać opcję liczenia wpisów (plików) zamiast wyświetlania ich nazw (oszczędzając miliony połączeń do printf, dla tej listy).
ChuckCottrill
29
Wiesz, że twój katalog jest za duży, gdy musisz napisać niestandardowy kod, aby wyświetlić jego zawartość ...
Casey
1
@ casey Tyle że nie musisz. Cała ta rozmowa o getdentskontra readdirnie ma sensu.
Mikel
9
Daj spokój! Ma już tam 5 milionów plików. Umieść swój niestandardowy program „ls” w innym katalogu.
Johan
12

Najbardziej prawdopodobnym powodem dlaczego jest powolny jest typ pliku farbowanie, można tego uniknąć z \lslub /bin/lswyłączenie opcji kolorów.

Jeśli naprawdę masz tyle plików w katalogu, finddobrym rozwiązaniem jest użycie zamiast tego.

Alex Lehmann
źródło
7
Nie sądzę, że to powinno być zaniżone. Sortowanie jest jednym problemem, ale nawet bez sortowania ls -U --colorzajęłoby dużo czasu, ponieważ dotyczyłoby statkażdego pliku. Więc oba są poprawne.
Mikel
Wyłączenie kolorowania ma ogromny wpływ na wydajność lsi jest domyślnie aliasowane w wielu wielu urządzeniach .bashrc.
Victor Schröder,
Tak, zrobiłem a /bin/ls -Ui dostałem wyjście w krótkim czasie, w porównaniu do czekania bardzo długo wcześniej
khebbie
-3

Uważam, że echo *działa znacznie szybciej niż ls. YMMV.

hymie
źródło
4
Powłoka posortuje *. Więc ten sposób jest prawdopodobnie nadal bardzo wolny dla 5 milionów plików.
Mikel
3
@Mikel Co więcej, jestem prawie pewien, że 5 milionów plików jest powyżej punktu, w którym globbing całkowicie się zepsuje.
evilsoup
4
Minimalna długość nazwy pliku (dla 5 milionów plików) to 3 znaki (być może 4, jeśli trzymasz się bardziej popularnych znaków) plus ograniczniki = 4 znaki na plik, tj. 20 MB argumentów poleceń. To znacznie powyżej wspólnej długości rozszerzonego wiersza poleceń o wielkości 2 MB. Exec (a nawet wbudowane) baulk.
Johan