Il comando ls non funziona per una directory con un numero enorme di file


70

Avevo una directory che conteneva circa 5 milioni di file. Quando ho provato a eseguire il lscomando dall'interno di questa directory, il mio sistema ha consumato un'enorme quantità di memoria e dopo un po 'si è bloccato. Esiste un modo efficiente per elencare i file oltre all'utilizzo del lscomando?


11
Assicurati di non avere un alias per quegli lsusi --coloro -Fperché ciò significherebbe fare un lstat(2)per ogni file.
Stéphane Chazelas,

4
A proposito, archiviare milioni di file in una singola directory è una pessima idea. Se controlli il layout della directory, forse lo dividi per alcuni criteri?
d33tah,

È stata una lschiamata pura o hai usato le opzioni?
Hauke ​​Laging,

1
@ d33tah Sì, 5 milioni sono molti! Il mio file system di root ha un limite di 7 milioni di inode.
Mikel,

7
5 milioni di articoli in uscita - come stai guardando questo - l'elenco semplice è troppo da vedere - quindi per cosa vuoi l'elenco?
user151019

Risposte:


66

Evita l'ordinamento usando:

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

O, equivalentemente:

ls -U

10
Mi chiedo anche quanto sovraccarico aggiunge il layout della colonna. L'aggiunta della -1bandiera potrebbe aiutare.
Mikel,

Probabilmente non molto, ma ogni piccolo aiuto, giusto? :)
Mikel

1
@Mikel È solo un'ipotesi o l'hai misurata? A me sembra che -1ci voglia ancora di più.
Hauke ​​Laging,

10
"-1" aiuta parecchio. "ls -f -1" eviterà qualsiasi chiamata stat e stamperà immediatamente tutto. L'output della colonna (che è l'impostazione predefinita quando si invia a un terminale) rende prima tutto il buffer. Sul mio sistema, usando btrfs in una directory con 8 milioni di file (come creato da "seq 1 8000000 | xargs touch"), "tempo ls -f -1 | wc -l" richiede meno di 5 secondi, mentre "tempo ls -f -C | wc -l "richiede oltre 30 secondi.
Scott Lamb,

1
@ToolmakerSteve Il comportamento predefinito ( -Cquando stdout è un terminale, -1quando è una pipe) è confuso. Quando si sperimenta e si misura, si passa tra la visualizzazione dell'output (per assicurarsi che il comando stia eseguendo ciò che ci si aspetta) e la sua soppressione (per evitare il fattore confondente del throughput dell'applicazione del terminale). Meglio usare i comandi che si comportano nello stesso modo in entrambe le modalità, in modo così esplicito definire il formato di output via -1, -C, -l, ecc
Scott Lamb

47

lsordina effettivamente i file e cerca di elencarli, il che diventa un enorme sovraccarico se stiamo cercando di elencare più di un milione di file all'interno di una directory. Come menzionato in questo link, possiamo usare straceo findelencare i file. Tuttavia, anche quelle opzioni mi sembravano irrealizzabili poiché avevo 5 milioni di file. Dopo un po 'di googling, ho scoperto che se si elencano le directory usando getdents(), si suppone di essere più veloce, perché ls, finde Pythonlibrerie usano readdir()che è più lento, ma utilizza getdents()sotto.

Possiamo trovare il codice C per elencare i file usando getdents()da qui :

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

Copia il programma C sopra nella directory in cui devono essere elencati i file. Quindi eseguire i comandi seguenti.

gcc  getdents.c -o getdents
./getdents

Esempio di tempi : getdentspuò essere molto più veloce di ls -f, a seconda della configurazione del sistema. Ecco alcuni tempi che dimostrano un aumento della velocità 40x per elencare una directory contenente circa 500k file su un mount NFS in un cluster di calcolo. Ogni comando è stato eseguito 10 volte in successione immediata, prima getdents, quindi ls -f. La prima esecuzione è significativamente più lenta di tutte le altre, probabilmente a causa di errori nella pagina di cache NFS. (A parte: su questo mount, il d_typecampo non è affidabile, nel senso che molti file appaiono come di tipo "sconosciuto".)

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
Potresti aggiungere un piccolo benchmark nei tempi per cui viene visualizzato il tuo caso ls?
Bernhard

1
Dolce. E potresti aggiungere un'opzione per contare semplicemente le voci (file) anziché elencare i loro nomi (salvando milioni di chiamate su printf, per questo elenco).
ChuckCottrill

29
Sai che la tua directory è troppo grande quando devi scrivere un codice personalizzato per elencare il suo contenuto ...
Casey

1
@casey Tranne che non è necessario. Tutto questo parlare di getdentsvs non è readdirall'altezza.
Mikel,

9
Dai! Ci sono già 5 milioni di file lì dentro. Inserisci il tuo programma "ls" personalizzato in un'altra directory.
Johan

12

Il motivo più probabile per cui è lento è la colorazione del tipo di file, puoi evitarlo con \lso /bin/lsdisattivare le opzioni di colore.

Se hai davvero tanti file in una directory, usare findinvece è anche una buona opzione.


7
Non penso che questo avrebbe dovuto essere ridimensionato. L'ordinamento è un problema, ma anche senza ordinamento, ls -U --colorrichiederebbe molto tempo poiché sarebbe statogni file. Quindi entrambi sono corretti.
Mikel

Disattivare la colorazione ha un impatto enorme sulle prestazioni di lsed è alias di default in molti molti .bashrclà fuori.
Victor Schröder,

Sì, ho fatto una /bin/ls -Ue ho ottenuto l'output in pochissimo tempo, rispetto ad aspettare molto tempo prima
khebbie,

-3

Trovo che echo *funziona molto più veloce di ls. YMMV.


4
La shell ordinerà il *. Quindi in questo modo è probabilmente ancora molto lento per 5 milioni di file.
Mikel,

3
@Mikel Oltre a questo, sono abbastanza sicuro che 5 milioni di file sono sul punto in cui i globbing si romperanno del tutto.
evilsoup,

4
La lunghezza minima del nome del file (per 5 milioni di file) è di 3 caratteri (forse 4 se rimani su caratteri più comuni) più delimitatori = 4 caratteri per file, ovvero 20 MB di argomenti di comando. Questo è ben oltre la lunghezza della riga di comando estesa di 2 MB. Exec (e persino i builtin) sarebbero rimasti indietro.
Johan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.