commit 7b218213f33b3a7a043f522ef17fbad718088076
parent 921fae969b2e1e7b046aef5ff7cb4ffe46366762
Author: fjbalon <fbalon@templier.es>
Date: Sat, 6 Jun 2026 01:43:52 +0200
Añadida entrada sobre el tamaño de bloque de dd
Diffstat:
| A | dd/index.html | | | 136 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 136 insertions(+), 0 deletions(-)
diff --git a/dd/index.html b/dd/index.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+<meta name="viewport" content="width=device-width, initial-scale=1" />
+<title>Sobre el tamaño de bloque en dd</title>
+<link rel="icon" type="image/png" href="/favicon.png" />
+<link rel="stylesheet" type="text/css" href="../article.css" />
+</head>
+<body>
+<header>
+<p class="title">Sobre el tamaño de bloque en dd</p>
+<p class="date">16 de marzo de 2021 • fjbalon</p>
+</header>
+<p>dd es una de las herramientas invaluables de cualquier arsenal Unix, y quizás una de las herramientas más potentes que existen en esta familia. Forma parte de las core utils y su objetivo principal es convertir y copiar archivos -de manera similar a cp-, dd hace una copia bit a bit con funciones de control de flujo de entrada y salida a bajo nivel. </p>
+<p>Permite entonces realizar tareas generales, como quemar una imagen -generalmente ISO- en un medio de instalación USB.</p>
+<pre><code>dd bs=512K if=/image.iso of=/dev/sdx conv=fsync oflag=direct status=progress</code></pre>
+<p>El uso de dd es simple, indicamos como parámetros un archivo de entrada (input file - if) y uno de salida (output file - of). De tal forma, también permite clonar un disco completo en el sistema de ficheros, siempre que indiquemos como if el disco físico (por ejemplo <emb>/dev/sdx</emb>) y como of la imagen a crear. Para mayor eficiencia y uso de recursos de espacio de disco, lo comprimimos con <emb>gz</emb> (tenga en cuenta que gzip sólo comprime utilizando un núcleo de la CPU, por lo que en discos de tamaño considerable, recomiendo utilizar otra herramienta como <emb>pigz</emb> para optimizar al máximo el tiempo de compresión):</p>
+<pre><code>dd bs=512K if=/dev/sdx status=progress | gzip -c > /image.img.gz</code></pre>
+<p>Del mismo modo, intercambiando if y of podemos quemar esta imagen comprimida en cualquier otro disco, sea <emb>/dev/sdy</emb>:</p>
+<pre><code>gunzip -c /image.img.gz | dd bs=512K of=/dev/sdy status=progress</code></pre>
+<p>Si lo único que deseamos es clonar de un disco a otro, podemos evitar el intermediario y hacerlo directamente jugando con if y of, dados los discos <emb>/dev/sdx</emb> y <emb>/dev/sdy</emb>:</p>
+<pre><code>dd bs=512K if=/dev/sdx of=/dev/sdy status=progress</code></pre>
+<p>Quizá sea porque hay que ser extremadamente cauteloso utilizando dd, ya que es sencillo destruir datos de forma irreversible en caso de negligencia; lo que hace que muchas veces nos enfoquemos únicamente en acertar con el parámetro of (archivo de salida - output file), o quizá sea pereza mental, pero un aspecto que se pasa normalmente por alto es el relativo a los tres argumentos de tamaño de bloque: ibs (tamaño de bloque de entrada), obd (tamaño de bloque de salida) y bs (tamaño de bloque de entrada y salida, abarcándolo todo).</p>
+<p>Partamos de la base de que un bloque es una unidad que mide el número de bytes que se leen, escriben o convierten a la vez. De forma general y predeterminada, dd utiliza un tamaño de bloque de 512 bytes, tanto para la entrada como para la salida. Esto quizás ha sido correcto entre 1980 y 1999, cuando la mayoría de discos tenían un tamaño de sector de 512 bytes, sin embargo en los últimos años la mayoría de los discos tienen un tamaño de sector de al menos 4096 bytes (4K). Aunque el cambio puede parecer intrascendente, puede conducir a ineficiencia e inestabilidad, más teniendo en cuenta que los discos actuales pueden tener más de 1TB de capacidad, lo que requiere un tamaño de bloque más óptimo.</p>
+<p>Por convención, hoy en día se recomienda utilizar un tamaño de bloque de 64K (en ocasiones se indica como 128K), asegurándose como más óptimos. Sin embargo, al no existir contexto ni explicación, lo considero un argumento viciado y arbitrario, teniendo en cuenta que no existe un tamaño de bloque universalmente correcto: lo que es óptimo para una situación puede ser terriblemente ineficiente para otra.</p>
+<p>Si tomamos como ejemplo de lectura el fichero <emb>/dev/zero</emb> y lo escribimos en un disco de 1TB SSD, producimos un rendimiento de 39,6 MB/s tomando el tamaño de bloque predeterminado de 512 bytes. Esto daría una duración de 26.479 segundos, unas 26 horas. Para el mismo caso pero en un disco de 1TB HDD, el rendimiento descendería a 10,9 MB/s, es decir, 96.200 segundos o unas 26 horas. Si tomamos el tamaño de bloque recomendado de 64K obtenemos un rendimiento de 266 MB/s (3.942s, ~1,1h) para SSD y 108 MB MB/s (9.709s, ~2,7h) para HDD. Si lo que buscamos es velocidad óptima, evidentemente cuanto mayor sea el tamaño de bloque, menos llevará a dd realizar la copia.</p>
+<p>Para ilustrarnos técnicamente, dado el script:</p>
+<pre><code>#!/bin/bash
+
+target=$1
+origin=/dev/urandom # /dev/zero
+file_SIZE=134217728 # 128MB
+
+human_bytes () {
+ unidades="b,K,M,G,T,P,E,Z,Y"
+ i=1
+ base=$(echo "$1 / 1" | bc)
+ variable=$( echo $base / 1024 | bc )
+ while [ $variable -gt 0 ]
+ do
+ let i=i+1
+ base=$(echo "$base / 1024" | bc)
+ variable=$(echo $base / 1024 | bc )
+ done
+ echo $base$(echo $unidades | cut -f$i -d, )
+}
+
+for BLOCK_SIZE in 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 1048576 2097152 4194304 8388608 16777216 33554432 67108864
+do
+ COUNT=$(($file_SIZE / $BLOCK_SIZE))
+
+ if [ $COUNT -le 0 ]; then
+ echo "Block size of $BLOCK_SIZE estimated to require $COUNT blocks, aborting further tests."
+ break
+ fi
+
+ # Clear kernel cache to ensure more accurate test
+ [ $EUID -eq 0 ] && [ -e /proc/sys/vm/drop_caches ] && echo 3 > /proc/sys/vm/drop_caches
+
+ DD_RESULT=$(dd if=$origin of=$target bs=$BLOCK_SIZE count=$COUNT conv=fsync 2>&1 1>/dev/null)
+ TRANSFER_RATE=$(echo $DD_RESULT | \grep --only-matching -E '[0-9.]+ ([MGk]?B|bytes)/s(ec)?')
+
+ printf "$BLOCK_SIZE ($(human_bytes $BLOCK_SIZE)) : $TRANSFER_RATE \n"
+done</code></pre>
+<p>Donde <emb><a href="https://balong.es/git/lab/file/unix/shell/humanize-bytes.sh.html">human_bytes</a></emb> se corresponde a una adaptación del script de «humanización» del resultado en bytes:</p>
+<pre><code>unidades=" bytes, KB, MB, GB, TB, PB, EB, ZB, YB"
+i=1
+base=$(echo "scale=2; $1 / 1" | bc)
+variable=$( echo $base / 1024 | bc )
+while [ $variable -gt 0 ]
+do
+ let i=i+1
+ base=$(echo "scale=2; $base / 1024" | bc)
+ variable=$(echo $base / 1024 | bc )
+done
+echo $base$(echo $unidades | cut -f$i -d, )</code></pre>
+<p>Cuyo resultado se basa en la escritura de datos provenientes de <emb>/dev/zero</emb> ó <emb>/dev/urandom</emb> en un disco pasado como argumento, de tal forma que analiza el rendimiento dado por el obs:</p>
+<pre><code>$ sudo ./dd-obs.sh /dev/sdd
+512 (512b) : 7 MB/s
+1024 (1K) : 9 MB/s
+2048 (2K) : 0 MB/s
+4096 (4K) : 192 MB/s
+8192 (8K) : 205 MB/s
+16384 (16K) : 204 MB/s
+32768 (32K) : 202 MB/s
+65536 (64K) : 195 MB/s
+131072 (128K) : 201 MB/s
+262144 (256K) : 203 MB/s
+524288 (512K) : 200 MB/s
+1048576 (1M) : 200 MB/s
+2097152 (2M) : 197 MB/s
+4194304 (4M) : 203 MB/s
+8388608 (8M) : 193 MB/s
+16777216 (16M) : 197 MB/s
+33554432 (32M) : 189 MB/s
+67108864 (64M) : 194 MB/s</code></pre>
+<p>Puede encontrar el script completo en <a href="https://balong.es/git/lab/file/unix/dd/calc-obs.sh.html">git</a>.</p>
+<p>Lo que lleva a analizar que un tamaño de bloques definido entre 64K y 512K es suficientemente óptimo, tomando como referencia valores más altos en los que apenas se notaría la optimización (y como detallaré más adelante ofrecerá más riesgo) y valores inferiores, contra los que sí que se observa una clara diferencia, especialmente tomando el valor por defecto.</p>
+<p>Para analizar el ibs, debemos indicar el disco como if y copiar el contenido of a <emb>/dev/null</emb>, teniendo una lógica similar a:</p>
+<pre><code>BLOCK_SIZE=65536
+COUNT=$(($file_SIZE / $BLOCK_SIZE))
+dd if=/dev/urandom of=$target bs=$BLOCK_SIZE count=$COUNT > /dev/null 2>&1
+
+for BLOCK_SIZE in 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 1048576 2097152 4194304 8388608 16777216 33554432 67108864
+do
+ DD_RESULT=$(dd if=$target of=/dev/null bs=$BLOCK_SIZE 2>&1 1>/dev/null)
+ TRANSFER_RATE=$(echo $DD_RESULT | \grep --only-matching -E '[0-9.]+ ([MGk]?B|bytes)/s(ec)?')
+ printf "$BLOCK_SIZE ($(human_bytes $BLOCK_SIZE)) : $TRANSFER_RATE \n"
+done</code></pre>
+<p>Ofreciendo un resultado similar, permitiéndonos ajustar el tamaño del bloque con mayor inteligencia:</p>
+<pre><code>$ sudo ./dd-ibs.sh example
+512 (512b) : 275 MB/s
+1024 (1K) : 373 MB/s
+2048 (2K) : 276 MB/s
+4096 (4K) : 491 MB/s
+8192 (8K) : 134 MB/s
+16384 (16K) : 348 MB/s
+32768 (32K) : 451 MB/s
+65536 (64K) : 486 MB/s
+131072 (128K) : 333 MB/s
+262144 (256K) : 406 MB/s
+524288 (512K) : 257 MB/s
+1048576 (1M) : 439 MB/s
+2097152 (2M) : 188 MB/s
+4194304 (4M) : 152 MB/s
+8388608 (8M) : 135 MB/s
+16777216 (16M) : 193 MB/s
+33554432 (32M) : 120 MB/s
+67108864 (64M) : 8 MB/s</code></pre>
+<p>Puede encontrar el script completo en <a href="https://balong.es/git/lab/file/unix/dd/calc-ibs.sh.html">git</a>.</p>
+<p>Hay que tener en cuenta que, si el tamaño de bloque es, digamos, 1M, dd leerá 1024x1024 bytes y escribirá bytes iguales. Pero si ocurriera un error de lectura, las cosas saldrán mal. Se tiende a pensar que dd llenará los errores de lectura con ceros si usa las opciones <emb>noerror</emb> y <emb>sync</emb>, sin embargo esto no es lo que sucede: dd rellenará el obs con el tamaño del ibs después de completar su lectura, lo que significa que agregará ceros al final del bloque. Es decir, para un disco todo el 1M se estropearía debido a un solo error de lectura de 512 bytes al comienzo de la lectura: <emb>12ERROR89</emb> se convertiría en <emb>128900000</emb> en lugar de <emb>120000089</emb>.</p>
+<p>Si tenemos la certeza de que el disco no contiene ningún error, podríamos continuar usando un tamaño de bloque más grande, lo que aumentará la velocidad de su copia varias veces, especialmente siguiendo los parámetros anteriores para una mayor eficiencia. Por ejemplo, en teoría cambiar el bs de 512 a 64K cambiará la velocidad de copia de 35MB/s a 120MB/s, tomando como ejemplo un sistema Celeron de 2.7 GHz. Pero tenga en cuenta que los errores de lectura en el disco de origen terminarán como «errores de bloque» en el disco de destino, es decir, un solo error de lectura de 512 bytes dañará todo el bloque de salida de 64K.</p>
+</body>
+</html>
+\ No newline at end of file