Leggi la metà di un file di grandi dimensioni


19

Ho un file da 1 TB. Vorrei leggere dal byte 12345678901 al byte 19876543212 e inserirlo nell'output standard su una macchina con 100 MB di RAM.

Posso facilmente scrivere uno script perl che fa questo. sysread eroga 700 MB / s (il che va bene), ma syswrite eroga solo 30 MB / s. Vorrei qualcosa di più efficiente, preferibilmente qualcosa che è installato in ogni sistema Unix e che può fornire nell'ordine di 1 GB / s.

La mia prima idea è:

dd if=1tb skip=12345678901 bs=1 count=$((19876543212-12345678901))

Ma questo non è efficiente.

Modificare:

Non ho idea di come abbia misurato il syswrite errato. Questo offre 3,5 GB / s:

perl -e 'sysseek(STDIN,shift,0) || die; $left = shift; \
         while($read = sysread(STDIN,$buf, ($left > 32768 ? 32768 : $left))){ \
            $left -= $read; syswrite(STDOUT,$buf);
         }' 12345678901 $((19876543212-12345678901)) < bigfile

ed evita l' yes | dd bs=1024k count=10 | wcincubo.


il tuo comando conbs=1M iflag=skip_bytes,count_bytes
frostschutz il

Risposte:


21

Questo è lento a causa della dimensione del blocco piccolo. Usando una GNU recente dd( coreutils v8.16 + ), il modo più semplice è usare le opzioni skip_bytese count_bytes:

in_file=1tb

start=12345678901
end=19876543212
block_size=4096

copy_size=$(( $end - $start ))

dd if="$in_file" iflag=skip_bytes,count_bytes,fullblock bs="$block_size" \
  skip="$start" count="$copy_size"

Aggiornare

fullblockopzione aggiunta sopra come da risposta @Gilles . All'inizio ho pensato che potesse essere implicito count_bytes, ma non è così.

I problemi citati rappresentano un potenziale problema di seguito, se le ddchiamate di lettura / scrittura vengono interrotte per qualsiasi motivo, i dati andranno persi. Ciò non è probabile nella maggior parte dei casi (le probabilità sono leggermente ridotte dal momento che stiamo leggendo da un file e non da una pipe).


L'uso di a ddsenza l' opzione skip_bytese count_bytesè più difficile:

in_file=1tb

start=12345678901
end=19876543212
block_size=4096

copy_full_size=$(( $end - $start ))
copy1_size=$(( $block_size - ($start % $block_size) ))
copy2_start=$(( $start + $copy1_size ))
copy2_skip=$(( $copy2_start / $block_size ))
copy2_blocks=$(( ($end - $copy2_start) / $block_size ))
copy3_start=$(( ($copy2_skip + $copy2_blocks) * $block_size ))
copy3_size=$(( $end - $copy3_start ))

{
  dd if="$in_file" bs=1 skip="$start" count="$copy1_size"
  dd if="$in_file" bs="$block_size" skip="$copy2_skip" count="$copy2_blocks"
  dd if="$in_file" bs=1 skip="$copy3_start" count="$copy3_size"
}

Puoi anche sperimentare blocchi di dimensioni diverse, ma i guadagni non saranno molto drammatici. Vedi - C'è un modo per determinare il valore ottimale per il parametro bs su dd?


@Graeme non fallirà il secondo metodo se bsnon è un fattore di skip?
Steven Penny,

@StevenPenny, non sono sicuro di cosa stai arrivando, ma skipè un numero di blocchi, non byte. Forse sei confuso dal momento che skip_bytesviene utilizzato nel primo esempio, il significato skip è in byte lì?
Graeme,

Il tuo bsè 4,096, il che significa che non puoi saltare più accuratamente di quei 4,096byte
Steven Penny,

1
@StevenPenny, ecco perché ci sono tre diverse esecuzioni ddcon il primo e l'ultimo utilizzo bs=1per copiare i dati che non iniziano o finiscono con un allineamento a blocchi.
Graeme,

6

bs=1dice dddi leggere e scrivere un byte alla volta. C'è un sovraccarico per ciascuno reade la writechiamata, che rende questo lento. Utilizzare blocchi di dimensioni maggiori per prestazioni decenti.

Quando copi un intero file, almeno sotto Linux, l'ho scoperto cpe catsono più veloce didd , anche se specifichi una dimensione di blocco grande.

Per copiare solo una parte di un file, puoi eseguire il pipe tailin head. Ciò richiede coreutils GNU o qualche altra implementazione che deve head -ccopiare un numero specificato di byte ( tail -cè in POSIX ma head -cnon lo è). Un rapido benchmark su Linux mostra che questo è più lento di dd, presumibilmente a causa della pipe.

tail -c $((2345678901+1)) | head -c $((19876543212-2345678901))

Il problema ddè che non è affidabile: può copiare dati parziali . Per quanto ne so, ddè sicuro quando si legge e si scrive su un file normale - vedere Quando è adatto per copiare i dati? (o, quando sono read () e write () parziale) - ma solo finché non viene interrotto da un segnale . Con i coreutils GNU, puoi usare il fullblockflag, ma questo non è portatile.

Un altro problema ddè che può essere difficile trovare un conteggio di blocchi che funzioni, poiché sia ​​il numero di byte ignorati che il numero di byte trasferiti devono essere multipli della dimensione del blocco. Puoi usare più chiamate per dd: una per copiare il primo blocco parziale, una per copiare la maggior parte dei blocchi allineati e una per copiare l'ultimo blocco parziale - vedi la risposta di Graeme per uno snippet di shell. Ma non dimenticare che quando esegui lo script, a meno che tu non stia usando la fullblockbandiera, devi pregare che ddcopi tutti i dati. ddrestituisce uno stato diverso da zero se una copia è parziale, quindi è facile rilevare l'errore, ma non esiste un modo pratico per ripararlo.

POSIX non ha niente di meglio da offrire a livello di shell. Il mio consiglio sarebbe di scrivere un piccolo programma C per scopi speciali (a seconda di ciò che si implementa, è possibile chiamarlo dd_done_righto tail_heado mini-busybox).


Wow, non ho mai conosciuto il yes | dd bs=1024k count=10 | wcproblema prima d'ora. Cattiva.
Ole Tange,

4

Con dd:

dd if=1tb skip=12345678901 count=$((19876543212-12345678901)) bs=1M iflags=skip_bytes,count_bytes

In alternativa con losetup:

losetup --find --show --offset 12345678901 --sizelimit $((19876543212-12345678901))

E poi dd, cat... il dispositivo loop.


Sembra molto incentrato su Linux. Ho bisogno dello stesso codice per funzionare anche su AIX, FreeBSD e Solaris.
Ole Tange,

0

Ecco come puoi farlo:

   i=$(((t=19876543212)-(h=12345678901)))
   { dd count=0 skip=1 bs="$h"
     dd count="$((i/(b=64*1024)-1))" bs="$b"
     dd count=1 bs="$((i%b))"
   } <infile >outfile

Questo è tutto ciò che è veramente necessario - non richiede molto di più. In primo luogo dd count=0 skip=1 bs=$block_size1, lseek()oltre i normali file di input praticamente istantaneamente. Non vi è alcuna possibilità di perdere dati o qualsiasi altra falsità venga comunicata al riguardo, puoi semplicemente cercare direttamente la posizione di partenza desiderata. Poiché il descrittore di file è di proprietà della shell e gli utenti lo ddstanno semplicemente ereditando, influenzeranno la sua posizione del cursore e quindi è sufficiente eseguirlo a passi. È davvero molto semplice - e non esiste uno strumento standard più adatto all'attività di dd.

Che utilizza una dimensione di blocchi di 64k che è spesso l'ideale. Contrariamente alla credenza popolare, blocchi più grandi non rendono il ddlavoro più veloce. D'altra parte, neanche i minuscoli buffer vanno bene. dddeve sincronizzare il suo tempo nelle chiamate di sistema in modo che non debba attendere la copia e la disconnessione dei dati, ma anche che non debba attendere le chiamate di sistema. Quindi vuoi che ci voglia abbastanza tempo che il prossimo read()non debba aspettare l'ultimo, ma non così tanto che stai bufferando in dimensioni maggiori di quanto sia necessario.

Quindi il primo ddpassa alla posizione iniziale. Ci vuole zero tempo. Potresti chiamare qualsiasi altro programma che ti è piaciuto a quel punto per leggere il suo stdin e inizierà a leggere direttamente all'offset di byte desiderato. Ne chiamo un altro ddper leggere i ((interval / blocksize) -1)blocchi di conteggio su stdout.

L'ultima cosa che è necessaria è copiare il modulo (se presente) dell'operazione di divisione precedente. E quello è quello.

Non crederci, a proposito, quando le persone dichiarano fatti sul loro viso senza prove. Sì, è possibile ddfare una breve lettura (anche se tali cose non sono possibili quando si legge da un dispositivo a blocchi sano, quindi il nome) . Tali cose sono possibili solo se non si bufferizza correttamente un ddflusso che viene letto da un dispositivo a blocchi. Per esempio:

cat data | dd bs="$num"    ### incorrect
cat data | dd ibs="$PIPE_MAX" obs="$buf_size"   ### correct

In entrambi i casi ddcopia tutti i dati. Nel primo caso è possibile (anche se improbabile cat) che alcuni dei blocchi di output che ddcopiano siano bit uguali a "$ num" byte perché ddè specificato solo per bufferizzare qualsiasi cosa quando il buffer è specificamente richiesto sul suo comando- linea. bs=rappresenta un massimo blocco-size perché lo scopo di ddè in tempo reale i / o.

Nel secondo esempio specifico esplicitamente l'output a blocchi e le ddletture dei buffer fino a quando non è possibile effettuare scritture complete. Ciò non influisce sul fatto count=che si basa su blocchi di input, ma per questo hai solo bisogno di un altro dd. Eventuali informazioni errate fornite altrimenti devono essere ignorate.

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.