Estructuras de datos

Blosc: agilizando el acceso a los datos en computación

En los últimos años se ha puesto de relieve que la velocidad a la que operan los microprocesadores crece mucho más rápido que la velocidad a la que se transfieren datos de la memoria al microprocesador.

Precisamente, para evitar esta asimetría Francesc Alted ha diseñado Blosc.

Blosc es un compresor optimizado para datos binarios. Comprime los datos con los que realizar computaciones, de manera que además de ocupar menos memoria, tardan menos tiempo en ser transferidos.

Con Blosc, por cada bloque de datos con el que trabajar, hay que transferir los datos, descomprimirlos, trabajar con ellos, comprimir los resultados y volver transferirlos.
Frente al proceso normal de transferir, computar y volver a transferir, consigue agilizar el tiempo empleado ya que en comprimir y descomprimir tarda muy poco frente a lo que se tarda en transferir los datos descomprimidos, precisamente por la enorme diferencia entre la velocidad de los micros y de las memorias.

Francesc presentará en el EuroSciPy 2009 este sistema, aplicándolo a las PyTables.

computación
Estructuras de datos

Comments (1)

Permalink

Comparando utilidades de compresión

Hoy ha tocado colocar un poco el disco duro, y en estas que me da por mirar cuánto ocupa el directorio

.thunderbird/

donde guardo todo el correo que recibo (desde hace años, incluído spam). 1.8G suman todos los correos. Casi nada, teniendo en cuenta que es texto plano.

Buceando en ese directorio llego al fichero

Inbox

de una de mis cuentas de correo. Todo lo que almacena está en formato mbox, que es lo que habitualmente usan los lectores de correo (o MUAs, Mail User Agent). Los MTAs (Mail Transport Agent) suelen usar el formato Maildir ya que evita el cuello de botella que impone mbox ante la concurrencia.

El caso es que me da por mirar a ver qué tal comprimen los diferentes algoritmos de compresión que tengo en Linux, que son básicamente cuatro: bzip2, LZMA (7zip y versiones nuevas del tar), el tradicional Zip (herramientas zip y gzip) y RAR.

Me he centrado sólo en el ratio de compresión de cada programa, no me he preocupado por cuánto tardan en comprimir, ni cuánto se tarda en listar el contenido de un fichero, o en descomprimir un sólo fichero. De hecho es que no he diferenciado entre formatos que comprimen y archivan a la vez, y los que sólo comprimen (p.ej.

bzip2

). He comprimido un sólo fichero, y era un fichero de texto (en formato

mbox

).

Los resultados para un solo fichero

mbox

de 118M:

Programa Algoritmo Tamaño ¿Libre?
gzip -9 LZ77 modificado 65M
zip -v9 LZ77 modificado 65M
bzip2 Burrous-Wheeler/Huffman 63M
rar LZSS 56M No
7-zip LZMA 54M
tar –lzma LZMA 54M

Con ¿Libre? me refiero a si yo conozco implementaciones libres del algoritmo tanto para comprimir como para descomprimir.

El LZ77 modificado también se conoce como DEFLATE.

Se concluye lo que más o menos ya habíamos observado a base bajarnos discos:

LZMA

es el que más comprime (y es libre),

RAR

no se queda muy lejos (pero no es libre, aunque es bastante multiplataforma),

bzip2

mejora un poco lo del tradicional

Zip

, pero éstos dos ya van teniendo que jubilarse.

Las únicas ventajas que tienen

zip/gzip/bzip2

son a) que aparentemente tardan menos en comprimir (lógico) y b) que están mucho más difundidos: es difícil encontrar un sistema que no tenga alguno de los tres.

LZMA

en cambio, es más difícil de encontrar.

Update (2009-02-17): Hoy en barrapunto.com han publicado Comparación entre diferentes algoritmos de compresión en Linux . Una noticia que enlaza a tres sitios donde han hecho comparativas similares, pero mucho más extensas:

Algoritmia
Estructuras de datos
Linux
Shell scripting

Comments (0)

Permalink

Recorrer un árbol de directorios en Python

Ya me picaba el gusanillo tras tanto examen y tanta gaita, así que esta vez ha tocado hacer una función que recorra los ficheros del directorio actual y de todos sus subdirectorios. Se basa en una función que mediante las funciones

os.stat()

para saber el tipo de fichero,

os.listdir()

para sacar el contenido de un directorio y una cola para guardar los ficheros que quedan por analizar.

Importamos las funciones necesarias de sus respectivos módulos:

from os import stat,listdir
from os.path import join
from stat import S_ISDIR,S_ISCHR,S_ISBLK,S_ISREG,S_ISFIFO,S_ISLNK,S_ISSOCK

Todas las

stat.S_ISxx()

se pueden sacar también de

os.stat.isXX()

, según la documentación del módulo stat –donde se puede encontrar una función similar a la implementada, pero con recursividad en vez de usar una cola.

Ahora la función (siento que la identación esté hecha con ___, pero es que el editor de WordPress no está hecho para meter código):

def walktree(origen):
____ficheros=[(None,origen),]

cargar la ruta inicial. El primer elemento de la tupla no sería necesario, no habría por qué usar una tupla. La estoy usando para un uso posterior que le quiero dar a esta función de recorrido de directorios.

____while ficheros:

mientras queden ficheros

____ ____ (parent,file)=ficheros.pop()

sacamos el siguiente fichero de la lista (ignorad

parent

)

____ ____print “Fichero: %s”%(file)
____ ____tipo=stat(file).st_mode

obtenemos mediante

os.stat()

el modo del fichero (lo que incluye los permisos, pero también el tipo). Según de qué tipo sea imprimiremos una cosa u otra en pantalla:

____ ____ if S_ISDIR(tipo):

Es un directorio: además de decir que es un directorio…

____ ____ ____ print “Directory”
____ ____ ____ for nuevo_fichero in listdir(file):

…debemos leer sus contenidos con

os.listdir()

e incluirlos en la lista ficheros. Nótese que

listdir()

no devuelve ni el directorio

‘.’

, ni

‘..’

, lo que nos evita ciclos que harían que el algoritmo no acabara nunca hasta agotar la memoria

____ ____ ____ ____ ficheros.insert(0,
____ ____ ____ ____ ____ (None,join(file,nuevo_fichero)))
____ ____ elif S_ISCHR(tipo):

y vamos comprobando otros tipos

____ ____ ____ print “Char. device”
____ ____ elif S_ISBLK(tipo):
____ ____ ____ print “Block device”
____ ____ elif S_ISREG(tipo):
____ ____ ____ print “Regular file”
____ ____ elif S_ISFIFO(tipo):
____ ____ ____ print “Named pipe”
____ ____ elif S_ISLNK(tipo):
____ ____ ____ print “Soft link”
____ ____ elif S_ISSOCK(tipo):
____ ____ ____ print “Socket”
if __name__==’__main__’:
____ walktree(‘.’)

Python
Sistemas de ficheros

Comments (0)

Permalink

Pilas y colas en Python

Las listas son un tipo de objeto mutable de Python. Por mutable, en Python se entiende que el objeto es modificable una vez definido, por ejemplo:

>>> a=[1,2]

(definimos la lista a con dos elementos)

>>> a.append(3)

(añadimos otro elemento)

>>> a
[1, 2, 3]

A diferencia de las tuplas, que no son modificables:

>>> b=(1,2)

(definimos la tupla b, que se quedará así por los siglos de los siglos…)

>>> b=None

(…o hasta que pase el recolector de basura)

Las listas por ser mutables tienen definidas unas operaciones sobre ellas de las que las tuplas carecen, por ejemplo el método append(<elemento>) que se ha visto antes, o por ejemplo los siguientes métodos:

>>> c=["a","b","c"]
>>> c
['a', 'b', 'c']
>>> c.insert(0,”d”)
>>> c
['d', 'a', 'b', 'c']
>>> c.insert(2,”e”)
>>> c
['d', 'a', 'e', 'b', 'c']
>>> c.pop()
‘c’
>>> c
['d', 'a', 'e', 'b']
>>>

Como se puede apreciar pop() toma el último elemento (, o c[len(c)-1], como se prefiera), lo saca de la lista (la lista se queda sin ese valor) y nos lo devuelve. Junto con append() es lo justo para crear una pila:

>>> pila=[]

(creamos la pila, vacía)

>>> pila.pop()

(sacamos datos de una pila vacía, y nos dan una excepción)

Traceback (most recent call last):
File “<stdin>”, line 1, in ?
IndexError: pop from empty list
>>> pila.append(1)

(añadimos datos a la pila)

>>> pila.append(2)
>>> pila.append(3)
>>> pila
[1, 2, 3]
>>> while pila:
… print “Ultimo elemento de la pila: %d (pila: %s)” % (pila.pop(),pila)…
Ultimo elemento de la pila: 3 (pila: [1, 2])
Ultimo elemento de la pila: 2 (pila: [1])
Ultimo elemento de la pila: 1 (pila: [])
>>>

El otro método utilizado era insert(<posición>,<elemento>), que como creo que cabe esperar realiza este comportamiento:

>>> lista=[]

(creamos la lista)

>>> lista.insert(1,”a”)

(insertamos en la posición 1, pero como está vacía realmente queda en la posición lista[0])

>>> lista
['a']
>>> lista.insert(5,”a”)

(insertamos en la posición 5, pero como está vacía realmente queda en la posición lista[1])

>>> lista
['a', 'a']
>>> lista.insert(0,”b”)

(insertamos al principio)

>>> lista
['b', 'a', 'a']
>>> lista.insert(-1,”c”)

(insertamos justo antes de la última posición)

>>> lista
['b', 'a', 'c', 'a']
>>> lista.insert(-2,”d”)

(insertamos en la antepenúltima posición)

>>> lista
['b', 'a', 'd', 'c', 'a']
>>>

¿Y para insertar en la última posición? lista.append(‘u’), mismamente:

>>> lista.append(‘u’)
>>> lista
['b', 'a', 'd', 'c', 'a', 'u']
>>>

Ahora ya crear una cola es coser y cantar, a base de insert(0,<elemento>) y pop():

>>> cola=[]

(crear la cola)

>>> cola.pop()

(sacar de una cola vacía nos da una excepción

IndexError

, como en la pila)

Traceback (most recent call last):
File “<stdin>”, line 1, in ?
IndexError: pop from empty list
>>> cola.insert(0,”1″) (

introducimos datos)

>>> cola.insert(0,”2″)
>>> cola.insert(0,”3″)
>>> cola
['3', '2', '1']
>>> while cola:

(y vamos sacando…)

… print “Cabeza de la cola: %c (cola: %s)” % (cola.pop(),cola)

Cabeza de la cola: 1 (cola: ['3', '2'])
Cabeza de la cola: 2 (cola: ['3'])
Cabeza de la cola: 3 (cola: [])
>>>

¿Cuánto tiempo nos podemos ahorrar por programarlo en Python en vez de en C?

Aunque también es mucho más ineficiente ejecutarlo en Python que en C, pero normalmente es preferible ahorrar tiempo de programación que tiempo de ejecución: mi tiempo es importante, el del microprocesador no tanto.

Estructuras de datos
Python

Comments (0)

Permalink

Atributos en ext2/3

Si hace unos días comentaba acerca de los atributos de los ficheros en la implementación de OpenBSD del Unix File System (o Fast File System), ahora mismo estaba viendo su equivalente en ext2, el que fuera hasta hace un par de años sistema de ficheros por defecto de Linux, sucedido por ext3 (que es compatible hacia atrás con ext2) y que dentro de poco será sucedido por ext4 (que de momento y por lo general sí es compatible con ext3).

Los dos comandos que permiten trabajar con los atributos de un fichero son lsattr y chattr, respectivamente para mostrarlos o cambiarlos. Ambos forman parte del paquete e2fsprogs.

Los susodichos atributos son 15, a saber:

  • ‘A’ evita que se modifique el campo atime (accesed time, última vez que fue accedido un fichero) del fichero cada vez que se accede a él. Puede reducir la carga de I/O del sistema.
  • ‘a’ indica que el fichero sólo se puede abrir para añadir si se abre para escritura. Tan sólo root o un proceso con el flag CAP_LINUX_INMUTABLE pueden activar o desactivar esta opción.
  • ‘c’ indica que el fichero se guardará comprimido. Por supuesto, la compresión será transparente al acceso al fichero. A la hora de acceder uno no tiene que preocuparse si el fichero está comprimido o no, eso es cosa del kernel. En las versiones actuales del kernel no está implementado, pero se prevee para versiones futuras.
  • ‘D’ implica actualizaciones síncronas de los directorios cada vez que son accedidos. Sólo es útil a partir de la rama 2.6.
  • ‘d’ excluye al fichero de los programas seleccionados por dump para ser copiados.
  • ‘E’ indica que un fichero tiene un error de compresión (los ficheros de la ‘c’). Es usado por los compresores en etapa experimental, y tan sólo puede ser visualizado mediante lsattr, no puede ser cambiado mediante chattr.
  • ‘i’ impide que se modifique el fichero o que se cree un enlace hacia él, que se elimine o se renombre. Sólo root o procesos CAP_LINUX_INMUTABLE pueden cambiar este flag.
  • ‘j’ hace que todos el fichero sea escrito primero en el journal de ext3 y luego en disco en caso de que el sistema de ficheros esté montado con los modos data=ordered o con data=writeback (con data=journal siempre se hace esto). Sólo root o procesos con la capability CAP_SYS_RESOURCE pueden trastear con esto. Sólo tiene sentido con ext3, no con ext2.
  • ‘s’ se asegura de que al borrar un fichero, todos sus bloques son rellenados con ceros en el disco. Esta forma de borrado segura presenta varias limitaciones. En las versiones actuales del kernel no está implementado, pero se prevee para versiones futuras.
  • ‘S’ actualiza los ficheros de forma síncrona. Similar a la opción sync de mount.
  • ‘T’ manda un directorio a la cima de la jerarquía de directorios. Está relacionado con el asignador de bloques de Orlov presente en la rama 2.6 del kernel desde que se portó la idea de BSD.
  • ‘t’ evita el tail-merging. A excepción de una serie de parches ext3 no cuenta con soporte para tail-merging.
  • ‘u’ guarda el contenido de un fichero cuando éste es borrado, permitiendo así la recuperación del fichero. En las versiones actuales del kernel no está implementado, pero se prevee para versiones futuras.
  • ‘X’, al igual que ‘E’ es usado por los compresores experimentales e indica (y sólo indica, por que no es modificable) que el fichero pese a estar comprimido puede ser accedido directamente.
  • ‘Z’ es similar al anterior, salvo porque indica que el fichero comprimido está sucio.

Desde luego, la variedad de opciones que presentan estos atributos es mayor que la del UFS de OpenBSD, y no sólo mayor si no también diferente. Lástima que Linux no cuente con los Secure Levels de OpenBSD porque a nivel de seguridad (más que de usabilidad) le confieren gran potencia.

Linux
Sistemas de ficheros

Comments (0)

Permalink