Member of The Internet Defense League Últimos cambios
Últimos Cambios
Blog personal: El hilo del laberinto Geocaching

Velocidad de disco duro en las lecturas aleatorias

Última Actualización: 24 de mayo de 2008 - Sábado

Los discos duros son lentos. Normalmente no se nota demasiado, porque o bien nos beneficiamos de la RAM del ordenador utilizada como memoria caché, o bien estamos leyendo un fichero largo en modo secuencial, que sí es una operación bastante rápida (un disco duro cualquiera moderno puede dar más de 50MB/s en modo secuencial).

Pero, a veces, manejamos un volumen de datos tan inmenso y accedemos a él de forma tan "desordenada", que no podemos evitar tropezarnos con la realidad física: los discos duros son lentos. Muy lentos.

Supongamos que tenemos un disco duro Seagate ST3250823A. Las especificaciones técnicas de este disco duro de 250 gigabytes indican que el tiempo medio de lectura es de 8 milisegundos. Es decir, que puede hacer unas 125 lecturas por segundo, si éstas están repartidas por todo el disco duro. Si leemos sectores aislados (512bytes), nuestro estupendo disco duro nos estará entregando apenas 60Kbytes por segundo.

El tiempo medio de lectura tiene dos componentes: el tiempo de "seek", o movimiento del cabezal, y el tiempo de rotación del disco. Dado que el disco duro es de 7200 revoluciones por minuto, o 120 revoluciones por segundo, el tiempo medio de rotación es de la mitad: 4.16 milisegundos. El tiempo medio de "seek", según el manual, es de menos de 11 milisegundos así que el tiempo medio de acceso tendría que ser la suma de ambos, unos 14-15 milisegundos. A saber cómo calcula el fabricante los 8 milisegundos que nos indica de tiempo medio de lectura. Un dato interesante es que el tiempo de "seek" a pistas cercanas es de 0.8 milisegundos.

En la práctica influyen muchos factores, como la distribución exacta de los accesos, la caché del disco duro, otros accesos concurrentes y el planificador de disco del sistema operativo, entre otros.

Pensando en mejoras para mi sistema de "backend" para Durus, una de las posibilidades que se me ocurren es disponer de varios hilos o "threads" para repartir las lecturas. De esta forma, cuando un cliente del sistema de persistencia quiere cargar en memoria cien objetos, por ejemplo, puede enviar al planificador de disco del sistema operativo (y éste al disco duro) las cien peticiones en paralelo, en vez de hacerlo de manera secuencial. De esta forma el sistema operativo y el disco duro tendrán más información y podrán optimizar los accesos al máximo.

¿Hasta qué punto es efectivo?.

Veamos el siguiente programa en Python:

import sys, threading, Queue, random, time

l=999997440
bs=4096
total_num_blocks=l//bs
num_blocks=total_num_blocks//10

blocks=range(num_blocks)
r=random.Random(0)
r.shuffle(blocks)
blocks=blocks[:num_blocks]

q=Queue.Queue(1000)

def read() :
  f=open("z","rb")
  while True :
    b=q.get()
    if b==None :
      q.put(None)
      f.close()
      return
    f.seek(bs*b)
    assert len(f.read(bs))==bs

threads=[]
for i in xrange(int(sys.argv[1])) :
  t=threading.Thread(target=read)
  t.setDaemon(True)
  t.start()
  threads.append(t)

t=time.time()
for i in blocks :
  q.put(i)

q.put(None)

for i in threads :
  i.join()

print num_blocks/(time.time()-t)

El experimento lo voy a ejecutar en tres máquinas distintas:

  • Linux 2.4.36.2, disco ATA de 250GB: Disco inactivo.

    En esta máquina uso volúmenes lógicos. Lo primero será crear uno de un gigabyte de capacidad, y formatearlo (como ext2). Luego lo montamos y creamos un fichero de 999997440 bytes:

    [root@yolco video]# lvcreate -L1G -n prueba LVM
    [root@yolco video]# mke2fs /dev/LVM/prueba
    [root@yolco video]# mount /dev/LVM/prueba /mnt
    [root@yolco video]# cd /
    [root@yolco /]# dd if=/dev/urandom of=/mnt/z bs=4096 count=244140
    24414+0 records in
    24414+0 records out
    [root@yolco /]# umount /mnt
    [root@yolco /]# mount /dev/LVM/prueba /mnt
    

  • Linux Linux 2.6.13, disco SATA de 160GB: Disco con cierto nivel de actividad, lo que perjudica el rendimiento, así que los datos no pueden compararse directamente.

    Creamos el volumen lógico, el fichero, etc., de la misma forma. Le daremos formato Reiser3, aunque no debería influir en el resultado.

  • Solaris 10 U5, dos discos SATA en "mirror", de 250GB cada uno: Hay cierta actividad de disco, pero pequeña. Dado que se trata de un "mirror" y ambos discos sirven para leer, el rendimiento debería ser muy superior al caso de los linux. Uso UFS.

Inicializamos el fichero con datos aleatorios para evitar cosas como la compresión de datos o la creación de "agujeros" en el fichero (cuando se graban ceros y el SO es "inteligente"). También hay que controlar que el volumen lógico utilice bloques físicos correlativos y en el mismo disco duro (si tenemos varios).

Por defecto, el programa lee un 10% de los datos (osea, 100Megabytes), para evitar la caché del sistema operativo. Los lee de forma aleatoria, para ponernos en el caso peor. Inicializamos el generador de números aleatorios con una constante para que sea reproducible y poder hacer comparaciones entre ejecuciones diferentes. En la línea de comandos indicaremos el número de hilos a utilizar.

Antes de cada ejecución, hay que desmontar el volumen lógico y volver a montarlo. De esta manera nos aseguramos de que no se queda nada en la caché del sistema operativo.

Accesos por segundo
Número
de hilos
Linux 2.4.36.2
250GB ATA
Linux 2.6.13
160GB SATA
Solaris 10 U5
2x250GB SATA
1300141251
2314150402
5311153411
10308153413
25306166433
100321167472
250317168489
1000317162500

Detalles:

  • En el caso de que la máquina tenga cierta carga de disco, además de nuestro experimento, aumentar el número de hilos nos beneficia porque nos llevaremos mayor "porcentaje" del disco. Es decir, si tenemos un programa muy activo en disco y dos hilos en nuestro experimento, el experimento se llevará, más o menos, 2/3 de los accesos a disco.

    En ese sentido, aumentar el número de hilos puede resultar rentable en la práctica, cuando la máquina está "cargada".

  • En igualdad de condiciones, a medida que el fichero crece, el rendimiento empeorará. La caché y el "read ahead" del sistema operativo será inefectivo. En el límite, con el fichero ocupando el disco duro entero, y lecturas aleatorias, estamos hablando de unas 125 peticiones por segundo.

  • El rendimiento del disco de 160GB es el previsto. En cambio el disco ATA de 250GB duplica la velocidad esperada. La única explicación que le veo es que se trata de un modelo para servidores, con 8MB de memoria caché. El "read ahead" del sistema operativo también puede tener algo que ver. En cualquier caso me sorprende el rendimiento.

    Si es así, a medida que el fichero crece, el rendimiento irá decreciendo (porque la caché del disco y el "read ahead" del sistema serán menos efectivas).

    Lamentablemente no tengo 50 gigabytes libres en un único disco, para poder hacer pruebas...

    Otra opción, creíble, es que el volumen lógico esté distribuyendo los datos entre varios discos duros (esa máquina tiene tres). El comando "lvdisplay" dice lo contrario, pero esto es más creíble que pensar que tengo un disco duro "extraordinario". Lo extraño, entonces es que no se vea el aumento de rendimiento entre usas un hilo o dos, tal y como se ve en Solaris. Y si LVM estuviese haciendo striping de los bloques de 4Kbytes, entonces tendría el mismo número de operaciones por segundo, pero con el doble de ancho de banda....

    Algo a investigar.

  • En Solaris no estoy usando ZFS. Es algo a investigar también en el futuro.


Historia

  • 24/may/08: Primera versión de este documento.



Python Zope ©2006-2007 jcea@jcea.es

Más información sobre los OpenBadges

Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS