static-httpd

Sitio web estático (HTML + CSS) que sostiene el blog del autor
Log | Files | Refs

index.html (10140B)


      1 <!DOCTYPE html>
      2 <html>
      3 <head>
      4 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
      5 <meta name="viewport" content="width=device-width, initial-scale=1" />
      6 <title>Sobre el tamaño de bloque en dd</title>
      7 <link rel="icon" type="image/png" href="/favicon.png" />
      8 <link rel="stylesheet" type="text/css" href="../article.css" />
      9 </head>
     10 <body>
     11 <header>
     12 <p class="title">Sobre el tamaño de bloque en dd</p>
     13 <p class="date">16 de marzo de 2021 • fjbalon</p>
     14 </header>
     15 <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>
     16 <p>Permite entonces realizar tareas generales, como quemar una imagen -generalmente ISO- en un medio de instalación USB.</p>
     17 <pre><code>dd bs=512K if=/image.iso of=/dev/sdx conv=fsync oflag=direct status=progress</code></pre>
     18 <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>
     19 <pre><code>dd bs=512K if=/dev/sdx status=progress | gzip -c  > /image.img.gz</code></pre>
     20 <p>Del mismo modo, intercambiando if y of podemos quemar esta imagen comprimida en cualquier otro disco, sea <emb>/dev/sdy</emb>:</p>
     21 <pre><code>gunzip -c /image.img.gz | dd bs=512K of=/dev/sdy status=progress</code></pre>
     22 <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>
     23 <pre><code>dd bs=512K if=/dev/sdx of=/dev/sdy status=progress</code></pre>
     24 <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>
     25 <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>
     26 <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>
     27 <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>
     28 <p>Para ilustrarnos técnicamente, dado el script:</p>
     29 <pre><code>#!/bin/bash
     30 
     31 target=$1
     32 origin=/dev/urandom # /dev/zero
     33 file_SIZE=134217728 # 128MB
     34 
     35 human_bytes () {
     36     unidades="b,K,M,G,T,P,E,Z,Y"
     37     i=1
     38     base=$(echo "$1 / 1" | bc)
     39     variable=$( echo $base / 1024 | bc )
     40     while [ $variable -gt 0 ]
     41     do
     42         let i=i+1
     43         base=$(echo "$base / 1024" | bc)
     44         variable=$(echo $base / 1024 | bc )
     45     done
     46     echo $base$(echo $unidades | cut -f$i -d, )
     47 }
     48 
     49 for BLOCK_SIZE in 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 1048576 2097152 4194304 8388608 16777216 33554432 67108864
     50 do
     51     COUNT=$(($file_SIZE / $BLOCK_SIZE))
     52 
     53     if [ $COUNT -le 0 ]; then
     54     echo "Block size of $BLOCK_SIZE estimated to require $COUNT blocks, aborting further tests."
     55     break
     56     fi
     57 
     58     # Clear kernel cache to ensure more accurate test
     59     [ $EUID -eq 0 ] && [ -e /proc/sys/vm/drop_caches ] && echo 3 > /proc/sys/vm/drop_caches
     60     
     61     DD_RESULT=$(dd if=$origin of=$target bs=$BLOCK_SIZE count=$COUNT conv=fsync 2>&1 1>/dev/null)
     62     TRANSFER_RATE=$(echo $DD_RESULT | \grep --only-matching -E '[0-9.]+ ([MGk]?B|bytes)/s(ec)?')
     63 
     64     printf "$BLOCK_SIZE ($(human_bytes $BLOCK_SIZE)) : $TRANSFER_RATE \n"
     65 done</code></pre>
     66 <p>Donde <emb>human_bytes</emb> se corresponde a una adaptación del script de «humanización» del resultado en bytes:</p>
     67 <pre><code>unidades=" bytes, KB, MB, GB, TB, PB, EB, ZB, YB"
     68 i=1
     69 base=$(echo "scale=2; $1 / 1" | bc)
     70 variable=$( echo $base / 1024 | bc )
     71 while [ $variable -gt 0 ]
     72 do
     73     let i=i+1
     74     base=$(echo "scale=2; $base / 1024" | bc)
     75     variable=$(echo $base / 1024 | bc )
     76 done
     77 echo $base$(echo $unidades | cut -f$i -d, )</code></pre>
     78 <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>
     79 <pre><code>sudo ./dd-obs.sh /dev/sdd
     80 512 (512b) : 7 MB/s 
     81 1024 (1K) : 9 MB/s 
     82 2048 (2K) : 0 MB/s 
     83 4096 (4K) : 192 MB/s 
     84 8192 (8K) : 205 MB/s 
     85 16384 (16K) : 204 MB/s 
     86 32768 (32K) : 202 MB/s 
     87 65536 (64K) : 195 MB/s 
     88 131072 (128K) : 201 MB/s 
     89 262144 (256K) : 203 MB/s 
     90 524288 (512K) : 200 MB/s 
     91 1048576 (1M) : 200 MB/s 
     92 2097152 (2M) : 197 MB/s 
     93 4194304 (4M) : 203 MB/s 
     94 8388608 (8M) : 193 MB/s 
     95 16777216 (16M) : 197 MB/s 
     96 33554432 (32M) : 189 MB/s 
     97 67108864 (64M) : 194 MB/s</code></pre>
     98 <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>
     99 <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>
    100 <pre><code>BLOCK_SIZE=65536
    101 COUNT=$(($file_SIZE / $BLOCK_SIZE))
    102 dd if=/dev/urandom of=$target bs=$BLOCK_SIZE count=$COUNT > /dev/null 2>&1
    103 
    104 for BLOCK_SIZE in 512 1024 2048 4096 8192 16384 32768 65536 131072 262144 524288 1048576 2097152 4194304 8388608 16777216 33554432 67108864
    105 do
    106     DD_RESULT=$(dd if=$target of=/dev/null bs=$BLOCK_SIZE 2>&1 1>/dev/null)
    107     TRANSFER_RATE=$(echo $DD_RESULT | \grep --only-matching -E '[0-9.]+ ([MGk]?B|bytes)/s(ec)?')
    108     printf "$BLOCK_SIZE ($(human_bytes $BLOCK_SIZE)) : $TRANSFER_RATE \n"
    109 done</code></pre>
    110 <p>Ofreciendo un resultado similar, permitiéndonos ajustar el tamaño del bloque con mayor inteligencia:</p>
    111 <pre><code>sudo ./dd-ibs.sh example
    112 512 (512b) : 275 MB/s 
    113 1024 (1K) : 373 MB/s 
    114 2048 (2K) : 276 MB/s 
    115 4096 (4K) : 491 MB/s 
    116 8192 (8K) : 134 MB/s 
    117 16384 (16K) : 348 MB/s 
    118 32768 (32K) : 451 MB/s 
    119 65536 (64K) : 486 MB/s 
    120 131072 (128K) : 333 MB/s 
    121 262144 (256K) : 406 MB/s 
    122 524288 (512K) : 257 MB/s 
    123 1048576 (1M) : 439 MB/s 
    124 2097152 (2M) : 188 MB/s 
    125 4194304 (4M) : 152 MB/s 
    126 8388608 (8M) : 135 MB/s 
    127 16777216 (16M) : 193 MB/s 
    128 33554432 (32M) : 120 MB/s 
    129 67108864 (64M) : 8 MB/s</code></pre>
    130 <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>
    131 <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>
    132 </body>
    133 </html>