Erlang: software para un mundo concurrente
Publicado por blaxter - 10 Abr 2008 a las 22:50Desde hace muchos meses quería meterle mano a Erlang. Lenguaje de programación funcional, concurrente y dinámico. Cada vez es más latente la importancia de la concurrencia, en unos años lo raro será ver procesadores de un solo core, o sistemas de un único nodo.
Acabo de empezar (con calma, que solo suelo leer en el bus) el libro de Programming Erlang de Joe Armstrong, que dado que es de la editorial de Dave Thomas, The Pragmatic Bookshelf me da muy buenas vibraciones.
Traduciré uno de los párrafos inicial del libro que crea unas expectativas realmente interesantes sobre este lenguaje creado por Ericson hace 21 años:
Erlang es un lenguaje donde la concurrencia pertenece al lenguaje de programación y no al sistema operativo. Erlang hace fácil la programación paralela modelando el mundo como un conjunto de procesos paralelos que únicamente pueden interactuar entre si mediante intercambio de mensajes. En el mundo de Erlang, hay procesos paralelos, pero no bloqueos ni métodos de sincronización y tampoco posibilidad de corrupción de memoria compartida, ya que no hay memoria compartida.
Los programas en Erlang pueden estar formados desde miles a millones de procesos extremadamente ligeros que pueden ejecutarse en un solo procesador, en uno de varios núcleos o en una red de procesadores.
Ahí queda eso. No sé si luego podré usarlo en entornos del MundoReal™ tan fácilmente como otros lenguajes más conocidos, pero solo por ver su enfoque al tratar los problemas creo que valdrá la pena. Por ahora ya tengo el interprete instalado y su equivalente a CPAN, llamado cean. Instrucciones para dummies, como un servidor (o eso me dice Amazon), en Ubuntu 7.10:
$ wget http://cean.process-one.net/download/cean.tar.gz
$ tar xfz cean.tar.gz
$ sudo mv cean-1.3/ /usr/lib/erlang/lib/
$ erl
Erlang (BEAM) emulator version 5.5.5 [source] [async-threads:0] [kernel-poll:false]
Eshell V5.5.5 (abort with ^G)
1> cean:version().
"CEAN Erlang/OTP"
Complexity
Publicado por blaxter - 09 Abr 2008 a las 17:46Fools ignore complexity. Pragmatists suffer it. Some can avoid it. Geniuses remove it.
Which group do you belong in?
Benchmark MySQL vs PostgreSQL vs SQLite vs MSAccess (vs ruby)
Publicado por blaxter - 05 Abr 2008 a las 00:19Por razones que no vienen al caso, me he encontrado hoy con un increíble WTF? usando MySQL (la lista no es corta, pero este era sorprendente, digno de Access o peor). He decidido hacer una comparativa con uno de sus competidores directos, PostgreSQL. Además probaré a intentar resolver el problema usando código en vez de dejar todo el “trabajo” a la base de datos, en concreto lo haré todo en ruby, no por eficiencia sino por comodidad (que sino me canso).
[Actualización 6 abril 2008 @ 19h] Ya puestos, he añadido también SQLite y Microsoft Access.
Presentemos el problema, tenemos dos tablas, A y B, cada una de ellas tiene una clave primaria compuesta por dos campos. Queremos averiguar las tuplas de la primera tabla que tienen como valor en uno de sus campos, valores que no se encuentran en ninguna tupla de la segunda tabla. Es decir una resta simplemente. Poniéndolo decentemente sería tal que:
- Tablas:
- A(id, otro_id)
- B(id, otro_id)
- Objetivo: tuplas de A para las cuales no existe ningún elemento en B cuyo valor del campo otro_id sea igual al campo otro_id de A
- Formalmente: x ɛ A. ∀y ɛ B y.otro_id != x.otro_id
- SQL: Lo más intuitivo y simple sería
SELECT * FROM A WHERE A.otro_id NOT IN (SELECT B.otro_id FROM B)
o usando LEFT JOINs también es simple expresarlo
SELECT * FROM A LEFT JOIN B USING (otro_id) WHERE B.otro_id IS NULL
Creo que es algo bastante evidente y simple de entender. Aplicado al MundoReal® puede surgir bastantes veces, no es que estemos antes un tipo de consulta retorcida ni nada por el estilo. Habría que resaltar que estamos usando parte de la clave primaria, en ambas tablas involucradas, por lo que en principio la intuición y nuestros conocimientos de bases de datos relaciones nos sugieren que esto va a ir más rápido que el correcaminos.
¿Cómo resolverías esta consulta?, No me hagas pensar, veamos qué nos dice Postgre:
QUERY PLAN
Seq Scan ON a (cost=189.91..379.84 rows=4877 width=24)
Filter: (NOT (hashed subplan))
SubPlan
-> Seq Scan ON b (cost=0.00..165.53 rows=9753 width=12)
Parece buen plan, primero hará un hash de los valores que se le indican en B, le da un coste de menos de 2 décimas (son milésimas los valores) y calcula que devolverá 9753 columnas (que son todas las que hay), luego dice que filtrará todos los de A que no se encuentren en ese hash. Es lo que le hemos pedido, correcto. A este segundo paso le da una estimación de menos de 2 décimas también, y cree que saldrán 4877 resutlados (esto son todo estimaciones, postgre ahora mismo no ha ejecutado nada, solo nos cuenta su vida).
MySQL es un poco más tímido y no da tanto detalle, pero también podemos pedir que nos explique qué va a hacer:
*************************** 1. row ***************************
id: 1
select_type: PRIMARY
table: A
type: index
possible_keys: NULL
key: PRIMARY
key_len: 178
ref: NULL
rows: 9754
Extra: USING WHERE; USING index
*************************** 2. row ***************************
id: 2
select_type: DEPENDENT SUBQUERY
table: B
type: index
possible_keys: NULL
key: PRIMARY
key_len: 178
ref: NULL
rows: 9753
Extra: USING WHERE; USING index
Básicamente dice que si, que va a usar clave primaria para ambas partes y punto. ¡Qué sabiduría!.
Ok, pues vamos a comparar. Podía simplemente ejecutar la consulta y santas pascuas, pero como me apetece comparar también cual es el coste a realizar trabajo de la base de datos vía código, he hecho un pequeño script en ruby, usando ActiveRecord para manejar las conexiones a la base de datos de forma simple. El código entero lo pongo targzeado al final, ahora pongo aquí la parte que interesa:
# uno! El brikindans!, digo consulta en MySQL "normal" con el NOT IN
b.report("(1) Consulta en MySQL") do
execute_with MySQL::A, :normal
end
# La misma que en (1) pero con PostgreSQL
b.report("(2) Consulta en PostgreSQL") do
execute_with PostgreSQL::A, :normal
end
# La misma que en (1) pero con SQLite3
b.report("(3) Consulta en SQLite3") do
execute_with Sqlite3::A, :normal
end
# Consulta con MySQL pero esta vez usando LEFT JOIN
b.report("(4) Consulta en MySQL (LEFT JOIN)") do
execute_with MySQL::A, :left_join
end
# La misma que en (4) pero con PostgreSQL
b.report("(5) Consulta en PostgreSQL (LEFT JOIN)") do
execute_with PostgreSQL::A, :left_join
end
# La misma que en (4) pero con SQLite3
b.report("(6) Consulta en SQLite3 (LEFT JOIN)") do
execute_with Sqlite3::A, :left_join
end
# Hacemos el proceso en código de forma penosa, con un coste Ɵ(n^2)
b.report("(7) Consulta en código (noob mode)") do
as = MySQL::A.find :all
bs = MySQL::B.find(:all).map{|i| i.otro_id }
as.select{|i| !bs.include?(i.otro_id) }.size
end
# Hacemos el proceso en código pero de forma decente, restando conjuntos
b.report("(8) Consulta en código (MySQL)") do
(MySQL::A.find(:all).map{|i| i.otro_id } -
MySQL::B.find(:all).map{|i| i.otro_id }).size
end
# La misma que en (8) pero con PostgreSQL
b.report("(9) Consulta en código (PostgreSQL)") do
(PostgreSQL::A.find(:all).map{|i| i.otro_id } -
PostgreSQL::B.find(:all).map{|i| i.otro_id }).size
end
# La misma que en (8) pero con SQLite3
b.report("(10) Consulta en código (SQLite3)") do
(Sqlite3::A.find(:all).map{|i| i.otro_id } -
Sqlite3::B.find(:all).map{|i| i.otro_id }).size
end
}
He aquí los resultados en mi pc, con MySQL 5.0.45, PostgreSQL 8.2.7 y SQLite 3.4.2 (sin tunear ninguna, tal y cual vienen en Ubuntu 7.10) y unos 10k registros en cada tabla.
Rehearsal ————————————————————————–
(1) Consulta en MySQL 0.020000 0.000000 0.020000 ( 54.567416)
(2) Consulta en PostgreSQL 0.000000 0.000000 0.000000 ( 0.100255)
(3) Consulta en SQLite3 0.180000 0.030000 0.210000 ( 0.295534)
(4) Consulta en MySQL (LEFT JOIN) 0.000000 0.000000 0.000000 ( 54.656340)
(5) Consulta en PostgreSQL (LEFT JOIN) 0.000000 0.000000 0.000000 ( 0.080631)
(6) Consulta en SQLite3 (LEFT JOIN) 47.980000 0.360000 48.340000 ( 54.249483)
(7) Consulta en código (noob mode) 21.660000 0.200000 21.860000 ( 30.060858)
(8) Consulta en código (MySQL) 0.520000 0.040000 0.560000 ( 0.696692)
(9) Consulta en código (PostgreSQL) 0.960000 0.060000 1.020000 ( 1.203388)
(10) Consulta en código (SQLite3) 3.410000 0.190000 3.600000 ( 4.141494)
—————————————————————- total: 75.610000sec
user system total real
(1) Consulta en MySQL 0.160000 0.010000 0.170000 ( 0.200753)
(2) Consulta en PostgreSQL 0.000000 0.000000 0.000000 ( 0.027388)
(3) Consulta en SQLite3 0.140000 0.010000 0.150000 ( 0.148729)
(4) Consulta en MySQL (LEFT JOIN) 0.010000 0.000000 0.010000 ( 0.000826)
(5) Consulta en PostgreSQL (LEFT JOIN) 0.000000 0.000000 0.000000 ( 0.025892)
(6) Consulta en SQLite3 (LEFT JOIN) 51.810000 0.370000 52.180000 ( 69.148641)
(7) Consulta en código (noob mode) 21.340000 0.200000 21.540000 ( 24.971090)
(8) Consulta en código (MySQL) 0.420000 0.010000 0.430000 ( 0.503471)
(9) Consulta en código (PostgreSQL) 0.970000 0.070000 1.040000 ( 2.188352)
(10) Consulta en código (SQLite3) 3.170000 0.120000 3.290000 ( 3.581979)
Adicionalmente he hecho también la prueba usando Microsoft Access 2003, gracias a este connector (que lo he usado para cargar todos los datos). No lo he incluido en las pruebas porque solo funciona bajo Windows, así que he ejecutado las consulta a mano. La primera de ellas le cuesta unos 200 seg aproximadamente mientras que el left join lo ejecuta casi al instante, no más de 0.25 segundos.
Mirando entonces todos los resultados, podemos observar como a MySQL le cuesta dos veces más que la ineficiente consulta con código y 700 veces más que a Postgre. Curioso, oye. En la segunda pasada MySQL ha cacheado el resultado (pero si haces otras consultas y volvemos a hacer ésta, tardaría de nuevo su tiempo “normal”) y es bastante más rápido.
En resumen se podría decir que es totalmente inaceptable (al menos para esta simple consulta de dos tablas) usar:
- MySQL (en cualquier caso)
- SQLite usando LEFT JOINs (pero funciona perfectamente con subconsulta)
- MS Access usando subconsulta (pero funciona perfectamente con LEFT JOINs)
- Hacer el trabajo en código de forma estúpida (es lo que tiene)
Por lo tanto, según mi propia interpretación, diría que nos quedan solo tres opciones (si consideramos únicamente estas 4 opciones como base de datos a usar) :
- Usar PostgreSQL
- Usar la base de datos ‘X’ para un X distinto a MySQL y probar nuestras consultas para ver si le gustan o no al SGBD
- Usar la base de datos ‘X’, para un X cualquiera, emplear consultas triviales y filtrar en código de forma decente. (opción poco viable para entornos reales cuando tengamos millones de registros y no solo 10 mil)
Y hasta aquí todo, que cada uno saque sus propias conclusiones. No voy a decir que MySQL es una puta mierda, o que PostgreSQL suele ser más lento que el caballo del malo, a decir verdad lo que si que diré es que los benchmark siempre son muy limitados y comparan cosas en un ámbito restringido y controlado, por lo que no sirven para nada, así que no sé para qué narices he escrito todo esto, a decir verdad no sé ni para que estás leyéndolo, pero allá tú.
El código, los dump para postgre y mysql listos para ser cargados en sus respectivas bases de datos, la base de datos en access y la base de datos de sqlite3 los dejó en este fichero por si quieres jugar un rato. Se requiere (aparte de las base de datos obviamente) active record y composite primary keys (ambos instalables como gemas, gem install composite_primary_keys, por ejemplo).
Hallowed are the Ori!
Publicado por blaxter - 01 Abr 2008 a las 17:59Después de cuatro meses de espera desde la reserva, y una larga demora por parte de Amazon, ya tengo en mis manos la primera película del final de Stargate SG-1, The Ark of Truth.
La película se ha publicado directamente en DVD (el 11 marzo), tuvo un presupuesto de $7M, y según estimaciones ya debería de haber pasado esa cifra (tres semanas más tarde de su estreno). En Amazon alcanzó el Top10 de ventas DVD al poco de salir y todavía se mantiene ahí en el puesto 7º. En resumen, creo que se podría hablar de un éxito bastante notable, aún más si tenemos en cuenta que solo ha sido publicada en USA.
La película, a decir verdad, estaba desde antes de su estreno por la red lista para ser descargada, pero incluso en los propios grupos de publicación de películas y series, se incitaba explícitamente a comprar el DVD aunque se bajase el dvdrip. Una actitud digna de mención y que, de nuevo, contradice todos los argumentos de energúmenos estúpidos que intentan mermar su deficientes resultados de taquilla echando la culpa a ese ambiguo y temido concepto llamado piratería.
Basta de palabrería, es turno de dar envidia cochina al personal :), si todavía no la tienes aprovecha que por nada la tienes en casa (con gastos de envío incluidos, solo 15€ aprox., es lo que tiene el dolar por los suelos…):

La película tiene audio en inglés, francés y español latino (parecen todos chiquito de la calzada) y subtítulos en inglés y español :). ¿Para qué más?. El doblaje en español (de España) de la saga Stargate es realmente bueno para las cosas que se oyen por ahí, pero las originales son insuperables (¡si no cómo narices vas a escuchar a Teal’c!).
Por cierto, si no se te ve será debido a que te faltan los codecs para ver dvd (libdvdcss2), en Ubuntu 7.10 simplemente:
$ sudo apt-get install libdvdread3 libxine1-ffmpeg totem-xine build-essential debhelper fakeroot
$ sudo /usr/share/doc/libdvdread3/install-css.sh
Dancing pigs
Publicado por blaxter - 28 Mar 2008 a las 17:22Given a choice between dancing pigs and security, users will pick dancing pigs every time
Who doesn’t?. This isn’t just a funny quote, this’s reality.
Abstracciones imperfectas
Publicado por blaxter - 24 Mar 2008 a las 22:30All non-trivial abstractions, to some degree, are leaky
Esta obra está bajo una
licencia de Creative Commons.
Este blog funciona gracias a WordPress
con el theme GimpStyle
diseñado por Horacio Bella y adaptado por un servidor.
Feed entradas