Leer la mitad de un archivo grande

Leer la mitad de un archivo grande

Tengo un archivo de 1 TB. Me gustaría leer del byte 12345678901 al byte 19876543212 y ponerlo en la salida estándar en una máquina con 100 MB de RAM.

Puedo escribir fácilmente un script en Perl que haga esto. sysread entrega 700 MB/s (lo cual está bien), pero syswrite solo entrega 30 MB/s. Me gustaría algo más eficiente, preferiblemente algo que se instale en todos los sistemas Unix y que pueda entregar del orden de 1 GB/s.

Mi primera idea es:

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

Pero eso no es eficiente.

Editar:

No tengo idea de cómo medí mal la escritura del sistema. Esto ofrece 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

y evita la yes | dd bs=1024k count=10 | wcpesadilla.

Respuesta1

Esto es lento debido al pequeño tamaño del bloque. Usando un GNU reciente dd(coreutils v8.16 +), la forma más sencilla es utilizar las opciones skip_bytesy 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"

Actualizar

fullblockopción agregada arriba según@Gilles responde. Al principio pensé que podría estar implícito en count_bytes, pero no es así.

Los problemas mencionados a continuación son un problema potencial: si ddlas llamadas de lectura/escritura se interrumpen por cualquier motivo, se perderán los datos. Esto no es probable en la mayoría de los casos (las probabilidades se reducen un poco ya que estamos leyendo desde un archivo y no desde una tubería).


Usar a ddsin las opciones skip_bytesy count_byteses más difícil:

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"
}

También puedes experimentar con diferentes tamaños de bloques, pero las ganancias no serán muy espectaculares. Ver -¿Hay alguna manera de determinar el valor óptimo para que el parámetro bs se agregue?

Respuesta2

bs=1indica ddleer y escribir un byte a la vez. Hay una sobrecarga para cada readllamada write, lo que hace que esto sea lento. Utilice un tamaño de bloque más grande para obtener un rendimiento decente.

Cuando copias un archivo completo, al menos en Linux, descubrí quecpy catson más rápidos quedd, incluso si especifica un tamaño de bloque grande.

Para copiar solo una parte de un archivo, puede ingresar taila head. Esto requiere GNU coreutils o alguna otra implementación que tenga head -cque copiar una cantidad específica de bytes ( tail -cestá en POSIX pero head -cno lo está). Una prueba comparativa rápida en Linux muestra que esto es más lento que dd, presumiblemente debido a la tubería.

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

El problema ddes queno es confiable: puede copiar datos parciales. Hasta donde yo sé, ddes seguro leer y escribir en un archivo normal; consulte¿Cuándo es adecuado dd para copiar datos? (o, cuando son parciales lectura() y escritura())- perosólo mientras no sea interrumpido por una señal. Con GNU coreutils, puedes usar la fullblockbandera, pero esto no es portátil.

Otro problema ddes que puede ser difícil encontrar un recuento de bloques que funcione, porque tanto el número de bytes omitidos como el número de bytes transferidos deben ser múltiplos del tamaño del bloque. Puede utilizar varias llamadas para dd: una para copiar el primer bloque parcial, otra para copiar la mayor parte de los bloques alineados y otra para copiar el último bloque parcial; consulteLa respuesta de Graemepara un fragmento de shell. Pero no olvide que cuando ejecute el script, a menos que esté usando la fullblockbandera, debe rezar para que ddse copie todos los datos. dddevuelve un estado distinto de cero si una copia es parcial, por lo que es fácil detectar el error, pero no existe una forma práctica de repararlo.

POSIX no tiene nada mejor que ofrecer a nivel de shell. Mi consejo sería escribir un pequeño programa en C de propósito especial (dependiendo exactamente de lo que implementes, puedes llamarlo dd_done_righto tail_heado mini-busybox).

Respuesta3

Con dd:

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

Alternativamente con losetup:

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

Y luego dd, cat, ... el dispositivo de bucle.

Respuesta4

Así es como lo haces, puedes hacer esto:

   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

Eso es todo lo que realmente se necesita, no hace falta mucho más. En primer lugar, la entrada de archivos normal dd count=0 skip=1 bs=$block_size1se realizará lseek()de forma prácticamente instantánea. No hay posibilidad dedatos perdidoso cualquier otra mentira que se diga al respecto, simplemente puede buscar directamente la posición inicial deseada. Debido a que el descriptor de archivo es propiedad del shell y los dd's simplemente lo heredan, afectarán la posición del cursor y, por lo tanto, puede hacerlo en pasos. Realmente es muy simple y no existe una herramienta estándar mejor adaptada a esta tarea que dd.

Utiliza un tamaño de bloque de 64k, que suele ser ideal. Contrariamente a la creencia popular, los tamaños de bloques más grandes no aceleran ddel trabajo. Por otro lado, los buffers pequeños tampoco sirven. ddnecesita sincronizar su tiempo en las llamadas al sistema para no tener que esperar a que se copien los datos en la memoria y volver a sacarlos, pero también para que no tenga que esperar a las llamadas al sistema. Por lo tanto, desea que tome suficiente tiempo para que el siguiente read()no tenga que esperar al último, pero no tanto como para almacenar en búfer tamaños más grandes de lo necesario.

Entonces el primero ddsalta a la posición inicial. Eso tomacerotiempo. Podrías llamar a cualquier otro programa que quisieras en ese momento para leer su entrada estándar y comenzaría a leer directamente en el desplazamiento de bytes deseado. llamo a otro ddpara leer((interval / blocksize) -1)contar bloques hasta la salida estándar.

Lo último que es necesario es copiar el módulo.(Si alguna)de la anterior operación de división. Y eso es eso.

Por cierto, no creas cuando la gente dice hechos a primera vista sin pruebas. Sí, es posible ddhacer una lectura breve.(aunque tales cosas no son posibles cuando se lee desde un dispositivo de bloque en buen estado, de ahí el nombre). Estas cosas sólo son posibles si no almacena correctamente una ddsecuencia que se lee desde un dispositivo que no sea de bloque. Por ejemplo:

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

En ambos casos ddcopiastodode los datos. En el primer caso es posible(aunque es poco probable con cat)que algunos de los bloques de salida que ddse copian tendrán un bit igual a "$num" bytes porque ddestá especificadosoloalmacenar en búfer cualquier cosa cuando el búfer se solicita específicamente en su línea de comandos. bs=representa unmáximotamaño de bloque porque elobjetivode ddes E/S en tiempo real.

En el segundo ejemplo, especifico explícitamente el tamaño del bloque de salida y ddlas lecturas en buffer hasta que se puedan realizar escrituras completas. Eso no afecta count=cuál se basa en bloques de entrada, pero para eso solo necesitas otro archivo dd. Cualquier información errónea que se le proporcione de otro modo debe ignorarse.

información relacionada