
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 | wc
pesadilla.
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_bytes
y 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
fullblock
opció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 dd
las 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 dd
sin las opciones skip_bytes
y count_bytes
es 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=1
indica dd
leer y escribir un byte a la vez. Hay una sobrecarga para cada read
llamada 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í quecp
y cat
son más rápidos quedd
, incluso si especifica un tamaño de bloque grande.
Para copiar solo una parte de un archivo, puede ingresar tail
a head
. Esto requiere GNU coreutils o alguna otra implementación que tenga head -c
que copiar una cantidad específica de bytes ( tail -c
está en POSIX pero head -c
no 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 dd
es queno es confiable: puede copiar datos parciales. Hasta donde yo sé, dd
es 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 fullblock
bandera, pero esto no es portátil.
Otro problema dd
es 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 fullblock
bandera, debe rezar para que dd
se copie todos los datos. dd
devuelve 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_right
o tail_head
o 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_size1
se 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 dd
el trabajo. Por otro lado, los buffers pequeños tampoco sirven. dd
necesita 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 dd
salta 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 dd
para 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 dd
hacer 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 dd
secuencia 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 dd
copiastodode los datos. En el primer caso es posible(aunque es poco probable con cat
)que algunos de los bloques de salida que dd
se copian tendrán un bit igual a "$num" bytes porque dd
está 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 dd
es E/S en tiempo real.
En el segundo ejemplo, especifico explícitamente el tamaño del bloque de salida y dd
las 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.