Últimos Cambios |
||
Blog personal: El hilo del laberinto |
Ú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:
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
Creamos el volumen lógico, el fichero, etc., de la misma forma. Le daremos formato Reiser3, aunque no debería influir en el resultado.
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 |
1 | 300 | 141 | 251 |
2 | 314 | 150 | 402 |
5 | 311 | 153 | 411 |
10 | 308 | 153 | 413 |
25 | 306 | 166 | 433 |
100 | 321 | 167 | 472 |
250 | 317 | 168 | 489 |
1000 | 317 | 162 | 500 |
Detalles:
En ese sentido, aumentar el número de hilos puede resultar rentable en la práctica, cuando la máquina está "cargada".
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.
Más información sobre los OpenBadges
Donación BitCoin: 19niBN42ac2pqDQFx6GJZxry2JQSFvwAfS