La vida digital

Ayer mi mamá me pidió que la acompañara a comprar una tele en Best Buy, porque así es la vida.

De pura chiripa encontramos una tele Samsung LED FullHD de 39″ en 5,999.99 pesos, porque estaba de promoción, así que le dije que se llevara esa, porque sí estaba muy barata. Ya pagando, vi que podía pagarse a 12 meses sin intereses con tarjetas de crédito Banamex; yo acabo de sacar la mía porque llevaban meses jode y jode los de Banamex con que sacara una, y porque con ella compré mi nuevo celular, así que le dije a mi mamá que me dejara pagarla con mi tarjeta de crédito y que luego ella me pagara las mensualidades.

Por supuesto mi tarjeta no pasó, porque con ella pagué el hotel de Oaxaca durante el congreso de la semana pasada y tengo como tres pesos de crédito; pero entonces se me ocurrió una idea: saqué mi celular, abrí mi aplicación de Banamex, y pagué mi tarjeta de crédito ahí mismo en la tienda, todavía enfrente de la caja donde la acababan de rebotar. Acto seguido, compré la tele de mi mamá con mi tarjeta de crédito, que ahora sí pasó.

Es chistoso, pero fue a la vez sorprendente y natural que pudiera hacer eso. Sorprendente porque nunca lo había hecho, y si hace algunos años me hubieran dicho que podría hacer algo así lo hubiera puesto altamente en duda… comenzando por el hecho de que yo tendría una tarjeta de crédito. Natural porque, vamos, es el mundo en el que ahora vivimos.

Ahora si tan solo pudiera comprar procesadores en Amazon México…

5 veces más líneas, 400 veces más rápido

Xochitl está a veces bajo ataque. En general son ataques idiotas que tratan de entrar por SSH usando combinaciones de usuario/clave del tipo “root/root” o “user1/user1″; evidentemente eso casi nunca funciona, y además esos ataques son automáticamente detenidos después de tres intentos fallidos con denyhosts.

Esos ataques no me dan problemas; me dan problemas los ataques dirigidos específicamente contra mi blog y/o galería en línea. No porque alguna vez hayan logrado nada (los mantengo actualizados); el problema es que a veces generan una cantidad tal de solicitudes que Apache comienza a sobrecargar MySQL, la base de datos queda trabada, y Apache entonces se queda atorado sirviendo páginas. Si los atacantes solicitan muchísimas solicitudes a la vez, esto causa que MySQL quede atorado con cientos de consultas en su cola, y por lo tanto que Apache quede atorado con cientos (o miles) de páginas que quieren ser servidas.

Como Apache trata de no tirar conexiones, y cada una de ellas utiliza procesador, esto hace que el CPU de Xochitl de repente se encuentre utilizado al 117%. Aquí es donde debo mencionar que Xochitl es una pobre Pentium 4 a 2.40 Ghz; es posible (y de hecho probable) que la mayoría de los teléfonos celulares inteligentes que han salido este año sean más rápidos (y tengan más memoria) que Xochitl.

De todo lo anterior no es esta entrada.

Esta entrada es acerca de una situación que encontré mientras buscaba qué poder hacer para aliviar a la pobre de Xochitl. La más sencilla es ver qué IPs están solicitando más conexiones HTTP, y agregarlas a /etc/hosts.deny (teniendo cuidado de no negarme acceso a mí y mis máquinas, o a los robots rastreros de Google). Suele funcionar; sobre todo considerando que estos “ataques” (la verdad ya no sé si son ataques o sólo lectores ligeramente stalkeadores de mi blog/álbum) no ocurren muy seguido.

Así que hice un programita que leyera los logs (o bitácora, si quisiera usar español correcto) de acceso de Apache, sacara las IPs, y contara cuántas veces aparece cada una. Como lo primero que aparece en cada línea es la IP solicitante seguida de un espacio, con el siguiente comando obtengo todas las IPs que solicitan páginas a Apache:

cat /var/log/apache2/access_log | cut -d " " -f 1

Hasta ahí vamos bien; ahora, ¿cómo saco de ahí cuántas veces se repite una IP?, porque sabiendo eso ya puedo saber cuáles IP solicitan un número ridículo de conexiones. Siendo, como soy, programador, escribí un programita que hiciera esto por mí. Lo escribí en Python, porque lo quería rápido, y esto me salió:

#!/usr/bin/env python

import sys

if __name__ == '__main__':
    ips = {}
    for line in sys.stdin.readlines():
        line = line.strip()
        if line in ips.keys():
            ips[line] = ips[line] + 1
        else:
            ips[line] = 1
    for ip in ips.keys():
        print('%d: %s' % (ips[ip], ip))

Esas son 14 líneas de Python, incluyendo el shebang y dos líneas en blanco. El programa lee línea a línea la entrada estándar, y usa un hash table para ir contando cada aparición de una IP.

Muy contento con mi programa lo corrí… y el maldito programa corrió, y corrió, y corrió, y siguió corriendo. Al minuto lo detuve, incrédulo de que pudiera ser tan endiabladamente lento. Lo revisé, lo puse a imprimir resultados intermedios, y el resultado era el mismo: es lentísimo.

Estúpido Python.

Me subí las mangas y lo reescribí en C, usando glib porque no me iba a a poner a escribir mi propio hash table (been there, done that). Esto me salió:

#include < stdio.h >
#include < string.h >
#include < glib.h >

typedef struct _integer integer;

struct _integer {
        int n;
};

static integer*
integer_new(int n) 
{
        integer* i = g_new(integer, 1);
        i->n = n;
        return i;
}

static char*
read_line(FILE* file)
{
        char line[4096];
        int i = 0;
        line[i] = (char)0;
        int c;
        while (TRUE) {
                c = fgetc(file);
                if (c == EOF || c == NEW_LINE)
                        return strdup(line);
                line[i++] = c;
                line[i] = char(0);
        }
        return strdup(line);
}

void
print_key_value(char* key, integer* value, gpointer user_data)
{
        printf("%d: %sn", value->n, key);
}

int
main(int argc, char* argv[])
{
        GHashTable* h;
        h = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
        do {
                char* line = read_line(stdin);
                if (!strcmp(line, "")) {
                        free(line);
                        continue;
                }
                char* key;
                integer* value;
                if (g_hash_table_lookup_extended(h, line, &key, &value)) {
                        value->n++;
                } else {
                        value = integer_new(1);
                        g_hash_table_insert(h, line, value);
                }
        } while (!feof(stdin));
        g_hash_table_foreach(h, print_key_value, NULL);
        g_hash_table_destroy(h);
        return 0;
}

Esas son 65 líneas en C, incluyendo la definición medio redundante de una estructura integer porque no quise usar las macros GINT_TO_POINTER y GPOINTER_TO_INT. No es elegante.

Ya que tuve mis dos versiones, según yo, equivalentes, las corrí ambas. La salida que producen es idéntica, así que me parece que sí son equivalentes. La versión en Python tarda más o menos 1 minuto 58 segundos (más/menos dos segundos, en todas las ocasiones en que lo corrí). La versión en C tarda 0.285 segundos, consistentemente debajo de 0.290. Esto para una bitácora de 95,080 líneas, de 12 Megabytes de tamaño.

La versión en C es unas 5 veces más larga en líneas que la de Python (de hecho 4.333, pero no importa), además de que las líneas tienen más caracteres; y sin embargo tarda (en tiempo de ejecución) del orden de 400 veces menos.

Ahí está el código si alguien quiere tratar de mejorar el resultado en Python. Yo estoy sumamente decepcionado; creí que las hash tables de Python estaban decentemente optimizadas: estoy usando cadenas como llaves al fin y al cabo. Y lo peor es que la versión en C ni siquiera me tomó mucho más tiempo en escribir.

Actualización: Gracias a Omar, ya vi que estaba cometiendo un errosote al buscar la llave en la hash table de Python; no tenía porqué buscarla en .keys() cuando puedo hacerlo directamente en la tabla. Con la sugerencia de Omar, el código Python es solamente el doble de lento que el código C. Lo que de hecho tiene sentido.

Dónde estás, que no te veo

En mi departamento tengo más computadoras de las que cualquier adulto viviendo solo debería tener. Más aún si cuento cosas como mi PlayStation 3 o mi teléfono celular, o incluso mi N800, por más que no lo haya usado en los últimos dos o tres años.

Como sea, casi todas las computadoras se conectan a mi red local vía inalámbrica, y todas lo hacen a través de DHCP. Todas por lo tanto suelen tener direcciones de IP dinámicas (excepto Atom, mi servidorcito con procesador Atom; como redirijo a él las conexiones SSH de mi módem DSL, necesito que tenga una dirección estática), y después de un rato es un desmadre estar viendo cuál computadora tiene qué dirección IP.

Para solucionar esto utilizo Avahi, una implementación de zeroconf para Linux, lo que causa que cada computadora con Linux que tengo anuncie su presencia en la red local. No importa qué dirección IP tenga Centurion, mi máquina de escritorio, dentro de mi LAN puedo accederla con la dirección centurion.local (desde una máquina que use Linux y Avahi). Como hay un navegador de zeroconf para Android, incluso lo puedo usar desde mi teléfono, aunque tengo que abrir el navegador, ver qué dirección tiene centurion.local y ya usarla.

Como sea; ayer salí un rato de mi departamento y al regresar vi que se había ido la luz, ya que Atom estaba apagado. Prendí de nuevo el servidorcito y mi máquina de escritorio (la había dejado suspendida), y traté de conectarme de nuevo a atom.local. No pude, así que supuse que Atom no había iniciado correctamente. Agarré mi teclado USB y lo llevé al servidorcito, usando mi televisión de 46″ como monitor (como Atom está en el mismo mueble que la tele, lo tengo conectado a ella todo el tiempo por VGA). Cuando entré al servidorcito, vi que, aparentemente, todo estaba bien. Traté de conectarme de Atom a Centurion, y tampoco lo encontró.

El protocolo de zeroconf funciona de tal forma que, bueno, necesita cero configuración. Lo dice ahí, en su nombre. Que algo falle con Avahi es bastante raro; de hecho no ha habido nuevas versiones en años; sencillamente funciona.

Regresé a Centurion, y me puse a investigar qué podía pasar. Ambas computadoras podían accesar Internet, sólo no la una a la otra. Dado que Avahi es un protocolo distribuido (no necesita un servidor), no pensé que fuera mi módem DSL, pero ya sin muchas otras opciones me metí al mismo para ver si todo jalaba bien con él; a lo mejor algo extraño había pasado con el servidor DHCP, o quién sabe.

Y entonces en el navegador apareció la página de autenticación del módem DSL. Y resultó que no era mi módem.

Cuando volvía a prender Centurion después de que regresó la luz, en lugar de conectarse a mi módem DSL, se conectó al módem DSL de un vecino, que todavía en esta época no ha aprendido que hay que ponerle clave. Conecté Centurion a mi módem DSL, y por supuesto se pudieron encontrar él y Atom. También borré el módem DSL de mi vecino de la lista de Access Points a los cuales Centurion trata de conectarse (no tengo idea de cómo llegó ahí en primer lugar), así que espero este particular problema no vuelva a repetirse.

Photo Locator

Cuando regresé de Guadalajara de ver a mi novia, traje conmigo cerca de 200 fotografías, y a casi todas ellas les pude sincronizar la información de localización GPS que saqué usando GPS Logger; platiqué de eso hace unas semanas. Cuando mi novia vino a verme durante semana santa, su despampanante belleza causó que me distrajera lo suficiente como para olvidárseme prender GPS Logger todos los días.

A ver, no se distraigan ustedes...

A ver, no se distraigan ustedes…

Esto, aunado con el hecho de que todas mis fotos antes de mi viaje a Guadalajara no tienen etiquetas GPS, y que tomé muy pocas fotos cuando Mina vino en las vacaciones, ha resultado en que el mapa de mis fotos en mi galería en línea se vea sí:

Mapa de la galería

Mapa de la galería

Según ese mapa, he tomado más fotos en Guadalajara que en la Ciudad de México. Casi diez veces más fotos. Y cero en cualquier otro país.

No me malentiendan; no tengo nada contra Guadalajara. Está bonito por allá. Pero el hecho de que la información de geolocalización de mi galería en línea refleje (erróneamente) que he tomado más fotos allá que aquí me tiene… incómodo, por decir lo menos. Y además, no refleja nada de los fabulosos viajes que he realizado por Europa y el resto de Norteamérica.

No hay manera de que automáticamente haga que las fotos sin información GPS adquieran dicha información. Bueno; técnicamente podría hacer un script que les pusiera coordenadas aleatorias a todas mis fotos, pero eso no serviría de mucho para lo que quiero: que el mapa de allá arriba refleje mis andanzas por todo el mundo. La única forma de conseguir eso es ir foto por foto tratando de recordar dónde estaba cuando la tomé.

El problema no es acordarme; tengo una extraordinaria memoria cuando se me pega la gana, y suelo recordar con bastante precisión dónde estaba cuando tomé una foto. El problema es la interfaz para asignarles coordenadas a las fotos; el plugin de Gallery 3 que genera el mapa de allá arriba también me permite ponerle coordenadas a cada foto, pero eso es usando dos cajitas de texto.

Editor de coordenadas

Editor de coordenadas

Esto significa que me tengo que ir a Google Maps (o algún otro programa que me permita ver las coordenadas, en latitud y longitud, de un lugar), agarrar esos numeritos, y pegarlos cada uno en su lugar en las cajitas de texto. Lo intenté hacer, y como a la tercera foto ya quería aventarme a las vías del metro para terminar con mi sufrimiento. Así que hice lo que cualquier programador que se respete haría; escribí un programa para ponerles coordenadas a mis fotos viejas.

Photo Locator

Photo Locator

Realmente fue trivial; tomé la mitad del código de otro de mis programas (Quick Photo Editor), le metí un actor con un ChamplainView (de la biblioteca libchamplain)… y básicamente ya. El actor hace todo, incluyendo bajar los mapas de OpenStreetMap, y me da las coordenadas de donde yo quiera en el planeta; es de verdad una biblioteca espectacularmente fácil de usar. De hecho es más rápido para mí ponerles coordenadas a mis fotografías que escribirles el título.

El programa es para mis fotos viejas; espero seguir usando GPS Logger para las futuras (siempre y cuando la despampanante belleza de mi novia no me siga distrayendo). Pero tengo más de 9,000 fotos, así que tardaré en ponerles coordenadas a todas; más en lugares como Europa, donde tendré que meterme al Street View de Google Maps para medio ubicarme dónde estaba cuando tomé la foto. Aunque ya decidí no angustiarme tanto; si no recuerdo rápidamente el lugar exacto donde tomé una foto, me voy a conformar con atinarle a la ciudad en el peor de los casos. Y como es complicado modificar la base de datos de coordenadas con fotos que ya subí, probablemente tendré que subirlas todas de nuevo cuando acabe.

Pero al menos mi galería en línea ya no dirá en su mapa que he tomado más fotos en Guadalajara que en la Ciudad de México.

Etiquetas GPS

Para mi viaje a Guadalajara instalé GPS Logger en mi teléfono Android. Antes de tener teléfono mamón, tenía mi Nokia N800, al cual le compré un GPS bluetooth, y con el cual podía ir grabando mi posición GPS cada segundo, para después transportar esa información a mis fotos.

Nunca funcionó muy bien que digamos; para empezar, el N800 no podía estar recabando mi posición GPS todo el tiempo vía bluetooth, por la sencilla razón de que la batería se moría a las doce horas, si uno tenía suerte. Además, mi GPS bluetooth sólo era eso, GPS; no soportaba AGPS (Assisted GPS), que se ayuda de las torres de celular para aproximar la posición de uno cuando no consigue engancharse a los satélites GPS (que pasa más comúnmente de lo que uno pensaría). Pero además se volvía todo una cosa muy incómoda, cargando el N800, el GPS, la cámara, y además con la angustia de que la batería se iba a morir en cualquier momento.

La única vez que intenté usarlo así fue cuando fui a ver a mi cuate Eddie en San Francisco en el 2009, y además de que pasó todo lo que acabo de describir (se murió la batería, el GPS no se conectaba a los satélites, fue un desmadre estar cargando tantos electrónicos), cuando regresé a México y traté de sincronizar la información que el N800 había estado guardando en mis fotos, resultó que no podía porque F-Spot había cambiado las horas de las mismas. Para que esto funcione bien, el reloj del GPS y el de la cámara debe estar casi perfectamente sincronizado, obviamente.

Creo que por ahí tengo la información GPS de ese viaje a San Francisco, pero la verdad ya ni intenté ver si podía ponérsela a mis fotos. Mi N800 ahora está arrumbado por ahí, y hace años que ni siquiera lo prendo.

Con mi teléfono Android todo ya es mucho más sencillo: tiene el GPS integrado, utiliza AGPS, y además la batería es bastante mejor que el del N800, así que como dije instalé GPS Logger y estuve guardando mi ubicación todo el tiempo que tomé fotos durante mi estadía en la capital jalisciense. Hoy que ya acabé la chamba que se me había acumulado por estar tres días casi sin ni siquera consultar mi correo electrónico, investigué cómo podía sincronizar dicha información en el par de centenas de fotos que tomé durante mi viaje.

Fue bastante sencillo: hay un programita llamado GPS Correlate que lo hace automágicamente, incluso con ventanitas y así. Igual y reescribo el programa, porque la verdad sí se siente medio abandonado; pero funcionó sin ningún problema. Ya que mis fotos tenían la información de ubicación dentro de ellas, vi qué podía hacer para que mi galería en línea la utilizase. También fue sencillo; sólo instalé el módulo EXIF GPS de Gallery3, y con esto aparece un mapita de Google Maps en cada uno de mis álbumes donde haya información de localización:

Mapa de álbum

Mapa de álbum

Y por supuesto, cada foto con información de localización tiene un mapita de Google Maps individual:

Mapa de foto

Mapa de foto

Está bastante chido; vaya a ser que se me olvide dónde tomé una foto. Me da coraje no haberlo hecho antes; tengo mi teléfono mamón desde junio del 2011, y en mi megaviaje de seis meses me hubiera sido bastante útil. Pero bueno, así se aprende en esta vida. Mi Android soporta un día entero de estar usando GPS Logger, pero apenas; termina con la batería debajo del 5%. Pero ya dentro de poco me tocará renovarlo, y espero que mi próximo teléfono tenga mucho mejor batería que el de ahora (y que jale más rápido, porque ya lo siento muy lento… y que tenga un buen de memoria interna, porque la que tengo es una grosería).

Así que a partir de ahora mis fotos tendrán la información de dónde las tomé, a menos que algo le pase a mi celular.

Con la cámara de mi nueva laptop

Después de que me hicieron notar, de la manera más directa posible, que me veía horrible en la imagen que puse hace unos días de mi vieja Creative Webcam NX, decidí poner screenshot de cómo aparezco en los hangouts de Google con la cámara de mi nueva laptop:

Con la cámara de mi nueva laptop

Con la cámara de mi nueva laptop

No, la calidad tampoco es espectacular, pero hago notar que era de noche, y además es un screenshot de la página del navegador donde estaba el hangout. Ya animado se ve bastante decente.

Y ciertamente me gusta más que usar Skype. Que al parecer en menos de un mes todos los usuarios de messenger serán migrados (de forma obligatoria) a Skype; durante años utilicé el messenger, pero tiene ya meses que ni siquiera me he conectado (solía causar que trabajara aún menos al escribir la tesis). No me queda claro que lo vaya a extrañar mucho.

En una década

He estado usando casi diario los Google+ Hangouts para videoconferencia (¿cómo se traduce eso?, ¿”pasadores de tiempo”?). Las razones son varias, pero la más importante, para mí, es que me evitan la molestia de instalar Skype, que cada vez me resultaba más desesperante.

Como he comentado no pocas veces en mi blog, detesto KDE, e incluido en eso va Qt. Compilarlo además en Gentoo es lentísimo, y prefiero usar mi procesador para cosas más interesantes. El cliente de Skype para Linux utiliza Qt, y al menos en Gentoo tienen la decencia de incluir una versión binaria de Qt junto con el paquete; pero de todas formas significa usar Qt, y si puedo siempre lo evito.

Además de Qt, Skype nunca mereció mucha confianza de mi parte; menos aún cuando fue comprado por Microsoft (que además casi garantiza que un buen cliente para Linux nunca existirá). Los Hangouts de Google+ me evitan todos estos problemas, y además Google me cae mejor que casi cualquier otra compañía, así que les doy el beneficio de la duda.

Técnicamente además los famosos hangouts están bastante padres; puede uno tener videoconferencia con N personas a la vez; corre todo dentro del navegador por lo que no es otra aplicación que hay que iniciar y configurar; y corre básicamente de forma perfecta en Linux, con un plugin para Chromium (que no dudo funcione también en Firefox) que mide en total como 21 Megabytes.

Comencé usando los hangouts en mi laptop, y la experiencia ahí ha sido básicamente perfecta excepto por dos problemas (ninguno relacionado con los hangouts): el primero es que mi laptop tiene unas bocinas que yo creo que son una broma, literalmente; y el segundo es que, por esas cosas que suelen ocurrir con Linux, el chipset que utiliza el bluetooth de mi laptop ahorita no está funcionando para que jalen mis audífonos bluetooth. Y hago énfasis en ahorita: hace unos meses funcionaba, y con casi toda seguridad en unos meses volverá a funcionar, sólo que como bluez pasó de la versión 4.101 a la 5, y no son compatibles, el proceso para que las aplicaciones que usan bluez se porten a la nueva versión puede tardar mucho. De hecho, es posible que con bluez 5 mis audífonos ya sirvan, sólo no he actualizado porque todo el resto del software sigue dependiendo de bluez 4.101.

Y para hacerlo más agraviante, al parecer todo lo relacionado con bluetooth funciona con bluez 4.101; incluso mis audífonos son reconocidos y ligados a la laptop. Sólo luego no aparecen como tarjeta de sonido externa. Como sea, no es grave; me pongo mis infalibles audífonos Genius, que además cuentan con micrófono incluido, y todo está chido.

De cualquier forma me dieron ganas de ver cómo funcionarían los hangouts en mi máquina de escritorio, en gran medida porque mi silla ahí es más cómoda, y el monitor es mucho más grande. Así que lo primero fue ver si servían bien los audífonos ahí; perdí unos quince minutos buscando dónde estaba el control de volumen para el micrófono, hasta que caí en cuenta de que PulseAudio es lo suficientemente inteligente como para no mostrarlo a menos que esté conectado.

Ni siquiera sabía que las computadoras ahora podían detectar cuándo estaba conectado un micrófono.

Luego fue la cámara de video, que es una antiquísima Creative Webcam NX que se conecta por USB a la computadora. Para que tengan una idea, la compré cuando pensé que me iría a hacer el posgrado a Canadá, así que sí tiene casi diez años conmigo; hablé de ella hace mucho. Bastante ha cambiado desde esa entrada; en particular, el controlador de la camarita está en el kernel desde hace años, y por lo que tengo entendido funciona perfecto… mientras en Windows a 64 bits de hecho nunca funcionó.

De nuevo tardé como veinte minutos tratando de encontrar mi camarita (el software; la camarita lleva años viviendo físicamente encima de mi monitor), hasta que por fin caí en cuenta de que por alguna razón el controlador no estaba compilado en mi kernel. En alguna actualización la opción de tener camaritas de video USB requirió alguna otra opción que no activé, y desde entonces no se había compilado el módulo. Así que recompilé el kernel, reinicié, y por fin tuve camarita de nuevo.

Y fue casi dolorosamente decepcionante:

Camarita

Camarita

Por supuesto las condiciones de luz en mi departamento apestan (más aún a las ocho de la noche), pero la calidad de la camarita es abismal. Tiene una resolución máxima de 352×288; en comparación, mi latop (que no es último modelo) tiene una resolución de 1280×1024. En mi monitor FullHD (1920×1080), la ventana de Cheese (el programa para tomar fotos de GNOME) tiene que escalar hacia arriba la imagen para que quepan los botones de su barra de herramientas. Y con tantito que se escale, a esa resolución, todo se ve súper pixeleado.

Así que descarté mi escritorio para usar los hangouts, a menos que mis interlocutores quieran ver sombras nada más. Lo que me impresionó, y que es el punto de esta entrada, es que hace diez años una camarita USB con resolución 352×288 no sólo no era rara, sino que tendía a estar en el grado alto del espectro. E independientemente de la resolución, la calidad del video (y fotos) que toman las camaritas actuales es muchísimo mejor que las de hace diez años.

En una década ocurrió que el hablar con alguien a cientos de kilómetros de distancia, y además verlos al mismo tiempo en tiempo real, se puede hacer de forma ridículamente sencilla, y con una calidad del video tal que uno puede ver los insectos caminando en las paredes detrás de los interlocutores (true story). A veces se me olvida que ya vivimos en el futuro.

Y por cierto, los hangouts funcionan sorprendentemente bien en teléfonos celulares inteligentes, si bien sólo los probé una vez, y ambos participantes usando red inalámbrica. Supongo que habrá que ver si funciona bien sobre 3G, aunque la verdad lo dudo. Mi teléfono no es 4G LT, pero me imagino que el próximo sí lo será.

Y entonces sí voy a sentir de verdad que vivimos en el universo de Star Trek.

Mis fotos

Gracias a mi aplicación para subir fotografías, y a mi aplicación para editar etiquetas, terminé ya de organizar, etiquetar, respaldar y subir todas mis fotos, incluyendo las que tomé el fin de semana pasado cuando Juan se casó.

Galería

Galería

En el procesó afiné y mejoré varias cosas de mis aplicaciones, y me parece que ya son suficientemente robustas. La metodología que tengo para manejar mis fotos está ya bastante estandarizada, y se integra de manera perfecta con GNOME 3, así que espero no volver a quedarme tan atrasado en el mantenimiento de mi galería. Además, como ahora tengo disco duro de sobra, decidí liberar a mi laptop de un montón de cosas (videos e imágenes de CDs y DVDs, principalmente) que no tenían razón de estar ahí, con lo que puedo ya tener una copia de mi colección de fotos en mi computadora portátil.

No lo he platicado en el blog, pero estoy estrenando laptop (desde hace varios meses), y tiene un disco duro de estado sólido; esto es espectacular porque todo corre estúpidamente rápido… la desventaja es que es diminuto para el tamaño de discos duros al que estoy acostumbrado (tiene 128 GB). Es la primera laptop donde borré completamente el Windows que venía instalado; necesitaba el espacio. Actualmente mi colección de fotos ronda en los 21 Gigabytes; dado que son del orden de 9,000 fotografías, espero no llegar a los 60 Gigabytes pronto: me quedan menos de 40 Gigabytes libres en la laptop. Cuando llegue a ese tamaño, espero ya haber cambiado de computadora portátil.

Tener las fotos en la laptop (y no sólo aventadas en un directorio, sino además ya bien integradas en Shotwell) me permitirá actualizar mi galería incluso si salgo de México en un viaje largo; podré organizar las fotos en mi laptop, e incluso subirlas a la galería en línea desde donde quiera que esté. Gran parte del problema durante mis viajes largos de los últimos años fue que la base de datos de mis fotos en F-Spot estaba en México en mi máquina de escritorio, y no sabía cómo sincronizar dos instalaciones distintas del programa en máquinas diferentes. Con Shotwell ya sé cómo hacerlo: es sólo es cosa de mantener mi directorio $HOME/Pictures sincronizado entre las dos máquinas, y copiar la última versión del archivo $HOME/.local/share/shotwell/data/photo.db sobre la versión viejita en la máquina que se esté sincronizando.

Además de la copia en mi máquina de escritorio y en mi laptop, tengo una copia de mis fotos en mi media center (que tendré que escribir un programa que me permita exportar la base de datos de Shotwell y meterla en la base de datos de XBMC, porque si no sólo se puede ver fotos por directorio), otra en mi servidorcito Atom, y una más en una máquina debajo de siete capas de adamantium y kriptonita que la tengo corriendo en la Zona Fantasma. Y una copia más (pero con las fotos escaladas a 1024×768) en Xochitl en mi galería en línea, que los invito una vez más a que la exploren.

Quick Photo Editor

Limpié y subí también mi aplicacioncita para editar etiquetas en fotos; la pueden encontrar en Github.

Quick Photo Editor

Quick Photo Editor

Llevo años escribiendo pequeñas aplicaciones que me quedo nada más para mí. No creo necesariamente que le vayan a servir a absolutamente nadie más, pero el hacerlas públicas me obliga a tener el código en buen estado, legible, y a escribir el mínimo de documentación e infraestructura necesarias para que no sea nada más un archivo en Vala, Python, C o Perl aventado en algún directorio de mi $HOME, que años después no tengo ni idea de qué hacía o por qué lo había escrito.

Esta aplicación está escrita en Vala, que me parece ahí reside en gran medida el futuro de GNOME; es muy divertido de programar, y los programas son razonablemente rápidos y con poco uso de memoria (contrario a C#). Además, el código es muy legible y compacto; no al grado de Python, pero me parece que sí más que C#. El programita, aunque su funcionalidad a lo mejor le es inútil a nadie que no sea yo, sirve también para estudiar un ejemplo pequeño, pero funcional, de cómo escribir una aplicación con autotools, usando gettext para internacionalización, cómo instalarle iconos, y otras cosas de ese estilo.

Gallery 3 Uploader

Total que limpié mi programita para subir fotos a Gallery3; es básicamente para mi uso privado, pero consideré que a lo mejor a alguien le podría resultar útil al menos el módulo que se comunica con el API REST de Gallery3.

Como lo iba a hacer público, lo limpié, le puse una interfaz gráfica, lo hice que hablara varios idiomas (inglés y español, porque no sé otros), y le puse toda la parafernalia para que se pueda compilar haciendo la santísima trinidad de ./configure && make && make install. El resultado no sólo es más agradable a la vista; ahora puedo seleccionar fotos en Nautilus, hacerles click derecho, y abrirlas con esta aplicacionciota, lo cual las manda a mi galería en línea.

Gallery 3 Uploader

Gallery 3 Uploader

Le faltan muchas cosas: por ejemplo, tronará sin decir nada si alguna de las etiquetas que espero encontrar en las fotos falta, pero ya es útil para mí, y espero lo sea también para alguien más. El programita está en Github: https://github.com/canek-pelaez/g3uploader.

Ahora sólo tengo que limpiar la aplicación que edita las etiquetas.

La vida a través de una cámara digital

En 2004, la Universidad de Waterloo me aceptó para que fuera a hacer mi maestría. Entre otras cosas por eso empecé este blog, porque quería escribir acerca de mis estudios en el extranjero. Por las mismas razones, estuve considerando desde febrero de 2005 el comprar una cámara digital, y en marzo Sergio, el hermano de Enrique, me hizo el favor de comprarme una Sony Cybershot DSC-P200 en el gabacho, que en abril por fin tuve en mis manos.

Por supuesto ya saben qué pasó: Conacyt decidió que la computación no era un “área estratégica” para México y no me becaron, así que me quedé aquí y pasaron los siguientes ocho años de mi vida. De eso no es esta entrada.

La entrada es de que una vez tuve mi cámara, de inmediato decidí que necesitaba un sitio en línea para poner mis fotos a la vista de todo mundo. Agarré e instalé Gallery, que era (y es, me parece) el programa más utilizado para mantener una galería en línea, y de inmediato me desagradó. Era lento, usaba mucho (y mal) JavaScript (o a lo mejor era sólo que entonces los navegadores traían pésimos motores de JS), y además no me gustó cómo se veía. Era la versión 1 del programa.

Instalé entonces Coppermine, que fue básicamente el primer programa alternativo a Gallery que encontré, y lo usé unos cuantos meses. En mi casa usaba F-Spot, que está escrito en C#, y que en ese momento me parecía un extraordinario programa. Claro, tenía entonces del orden de doce fotografías, entonces F-Spot hasta rápido parecía.

Mi colección de fotografías digitales estuvo durante varios años manejada, y de alguna manera controlada, por F-Spot. El programa no es para nada malo, sólo sufre el mismo problema que todos los programas escritos en C#: la memoria que consumen es ridículamente alta, y se vuelven lentísimos con no mucha información. Como sea, eso no lo descubriría sino hasta años después.

En 2005 todavía no lo descubría, porque les digo que tenía como quince fotos, pero mi uso de F-Spot causó que tuviera que dejar de usar Coppermine. F-Spot nunca fue pensado para usar álbumes; previendo que eso sería el futuro, F-Spot favorecía mejor las etiquetas, y entonces una foto puede pertenecer a más de una etiqueta. Uno puede emular la funcionalidad de álbumes con etiquetas, pero no al revés. Lo grave con Coppermine no fue que no tuviera etiquetas; era que no podía ni siquiera mover fotos entre álbumes. Además no podía subirlas fácilmente desde F-Spot (y en ese entonces la red era mucho más lenta), y todo se combinó para que decidiera dejar Coppermine.

Entonces volví a revisar Gallery, y seguía básicamente igual de malo que antes; pero por suerte ya estaba disponible el primer beta de Gallery2, así que decidí probarlo. Me gustó mucho más, pero lo que me convenció de usarlo fue que F-Spot tenía un plugin para mandar un conjunto de fotos a un álbum de Gallery2. Eso hizo mi vida mucho más sencilla.

Yo soy muy neurótico con mis colecciones, de lo que sea. De música, de películas, de videojuegos, o de fotos, me interesa que todo esté meticulosamente etiquetado y catalogado. En F-Spot podía ponerles a las fotos un comentario (donde generalmente pongo el nombre de los que aparecen en la foto, o el lugar donde estoy en el peor de los casos), y el plugin que subía las fotos a Gallery2 se encargaba de hacer que dicho comentario apareciera también en Gallery2. Era la gloria.

Así estuve durante años, muy feliz, aunque con varias incongruencias en mi galería en línea. Como F-Spot tenía varias etiquetas “principales” (Favoritos, Escondidas, Eventos, Lugares y Gente), yo traté de emular eso en Gallery2… lo cual es una enorme pendejada, porque terminé aventando casi todo al álbum de eventos. Además, por alguna razón que no comprendo, creé un álbum llamado “Pruebas” que aparecía ahí en la página principal de la galería, y que lo hizo durante varios años.

Como sea, quitando esas cosas todo medio funcionaba, y lo que más me importaba era que la información que metía con F-Spot a mis fotos, se conservaba cuando las subía a Gallery2. Hasta que un día los desarrolladores de F-Spot decidieron cambiar las cosas: lo que yo metía en F-Spot como comentarios a las fotos, se subía como el título a Gallery, pero entonces decidieron cambiarlo a que fuera el comentario. Y entonces no se veía esa información a nivel del álbum en Gallery2, se veía sólo en la página de la foto. Con eso podría haber vivido; lo que era horrible era que el título ahora era el nombre del archivo, algo del estilo dsc00768.jpg.

Pude parchar a F-Spot para que funcionara como lo hacía antes (de algo tiene que servir que sepa programar), pero ya para entonces, en 2008, se había comenzado a volver muy lento con todas mis fotos. Ya tenía del orden de 2,000 fotografías, y al programa le empezaba a pesar muchísimo. Además comenzó a estar súper inestable, y tenía que estar deshabilitando cosas para que no tronara.

A pesar de todo eso lo seguí usando, y aunque no de forma perfecta seguía funcionando lo más importante para mí, que podía pasar las fotos de F-Spot a Gallery2 preservando la información de las mismas.

Y entonces me fui a Europa durante tres meses, de enero a marzo de 2009, y se me ocurrió regresar con 2,500 fotografías más.

Cuando, después de meses, había metido todas las fotos de mi viaje a F-Spot, el programa ya era básicamente inusable para mí. Y además de todo, la subida de las mismas a Gallery2 no era raro que fallara de formas extrañas, lo cual dejaba una imagen de error en lugar de la foto (aunque sí generaba correctamente la miniatura, porque F-Spot era el que la generaba antes de subir la foto, lo que hacía todavía más difícil descubrir cuándo había fallado la transferencia).

Pero lo que hizo que me deshiciera de F-Spot fue que cuando regresé de California en 2009, donde visité a mi cuate Eddie en San Francisco, había estado guardando la información GPS de donde había estado, y decidí tratar de sincronizarla con las fotos (para que cada foto marcara dónde la había tomado). Y entonces me di cuenta de que las horas de las fotos estaba desfasadas por 6 horas, porque los idiotas de F-Spot estaban suponiendo que mi cámara estaba en horario GMT (UTC+0), y que como México (y mi escritorio) estaban configurados en America/Mexico_City (UTC-6), eso quería decir que tenía que moverle al tiempo de casi todas mis fotos.

No tienen idea de cómo me encabroné, porque nunca me preguntó o dijo nada al respecto, y yo pensaba que ya no podía rescatar el tiempo original. Así que cerré por última vez en mi vida F-Spot, y seguí trabajando en mi doctorado, viajando, y tomando fotos que aventaba al primer directorio que podía, sin ni siquiera pensar en que sería bueno algún día subirlas a mi galería.

Hasta que mi disco duro tronó, como comenté hace unos días.

Ya que recuperé mis fotos, y las respaldé en cuanta máquina pude respaldarlas, comencé a pensar en cómo restituir mi colección de fotos en mi máquina (sin usar F-Spot, claramente, que además al parecer dejaron ya de mantener: la última versión salió en 2010), y cómo después tener un sistema independiente de cualquier programa (o al menos de cualquier programa no escrito por mí) para sincronizarlo con mi galería en línea, que además migré a Gallery3 cuando me quedé sin novia, sin casa, sin dinero y sin trabajo.

Primero descubrí que las fechas modificadas por F-Spot eran las dadas por la etiqueta EXIF DateTimeOriginal, pero por suerte la fecha original estaba guardada en CreateDate, así que sólo escribí un script que comparara las dos fechas y reemplazara la primera con la segunda si acaso diferían; para eso utilicé exiftool. Luego decidí mover la información de F-Spot a las fotos directamente. Los comentarios que con tanto cuidado había metido a las mismas durante mis años de usar el programa estaban en una base de datos SQLite, así que escribí un programita en Python que sacaba esa información, y la guardaba en las etiquetas EXIF UserComment, Title, ImageDescription y Caption, porque me pareció que era mejor ser redundantemente redundante. Usé exiftool de nuevo para manipular las etiquetas de las fotos.

Ya que hice eso, decidí probar Shotwell, el nuevo programa para manejar fotos de GNOME 3. El programa está escrito en Vala, que todo el lenguaje me parece un maravilloso hack, y me gustó mucho cómo funciona. Sólo que entonces vi que en algunas fotos aparecían mis comentarios como títulos, y en otras sólo el nombre del archivo. Investigando (tuve que bajar hasta el código fuente de Shotwell), vi que lo que pasaba es que Shotwell usaba la etiqueta Iptc.Application2.Caption para el título, porque al parecer es lo más estándar. Esa etiqueta no es EXIF, es IPTC, así que tuve que usar el programa exiv2 para reacomodar los comentarios en mis fotos. Por suerte, todo esto ya era sólo escribir otro script que hiciera toda la chamba. También vi que Shotwell usa la etiqueta Xmp.dc.subject para etiquetas internas del programa, así que decidí que con eso haría mis álbumes.

Shotwell maneja álbumes a la antigüita, todos basados en fechas. Se pueden mezclar fotos entre álbumes, pero decidí mandar eso completamente al carajo: a partir de ahora, mis álbumes están definidos por un rango continuo de tiempo, y a la chingada con todo lo demás. Además de álbumes, Shotwell maneja etiquetas, pero son ortogonales los primeros a las segundas. De cualquier forma, decidí arbitrariamente que la única etiqueta que tendrían mis fotos sería el nombre del álbum al que pertenecen.

Como los álbumes de Shotwell están basados en tiempo, automaticamente divide todo en años, estos en meses, y ya dentro de los meses hay álbumes que pueden ser de un instante en el tiempo (si tienen una sola foto), o de varios días. Decidí que también así funcionaría mi galería en línea, y así es como reconstruí mi colección de fotos. Fue poco trabajo, en general, porque casi todo se pudo automatizar con scripts. Sólo tuve que reacomodar algunas fotos que no tenían un álbum bien definido, y de paso acomodé las fotos igual en la jerarquía del sistema de archivos: tengo un directorio 2009, dentro de él un directorio “02 Febrero”, y dentro de él un directorio para cada álbum, que como dije describen rangos continuos de tiempo.

Jerarquía de archivos

Jerarquía de archivos

La única bronca es cuando se me parte un evento que cae entre el último día de un mes y el primero del siguiente (los años nuevos suelen ser así), pero decidí que eso no era terriblemente grave. De esta forma, Shotwell no se mete para nada con mis fotos; jamás les escribe nada. Sólo lee información de ellas, y las despliega bonito, con lo que espero evitar las broncas que F-Spot me daba. Además, la organización de mis fotos en el disco duro es virtualmente idéntica a la organización de mis fotos en Shotwell.

Shotwell

Shotwell

Con mi colección reorganizada una vez más, decidí que necesitaba reestructurar mi galería en línea también. Inicialmente pensé en borrar las fotos que ahí estaban y meterlas todas de nuevo, pero resultó imposible: tuve que borrar todo y empezar de cero. Por suerte Gallery3 ofrece un API REST para manipular la galería en línea; está súper chido, muy fácil de programar, y me permite olvidarme de que nadie más me diga cómo deben subirse los datos a mi galería. Hice un programita en Python (versión 3; el uso de UTF-8 me impide que pueda usar Python 2) que le pasa uno una lista de archivos JPG, y les saca la información que me importa (básicamente la fecha, el título en Iptc.Application2.Caption y el álbum en Xmp.dc.subject), y procede como sigue:

  1. Saca el año de la foto, y comprueba que exista un álbum principal en la galería en línea llamado como el año. Si no existe, lo crea con la descripción “Año 2009″, por ejemplo.
  2. Saca el mes de la foto (01, …, 12), y comprueba que exista un álbum con ese nombre debajo del álbum del año correspondiente. Si no existe, lo crea con la descripción “Mes de Febrero”, por ejemplo.
  3. Saca el álbum de la foto, lo normaliza (quita acentos y símbolos, convierte espacios en guiones, etc), y comprueba que exista un álbum con ese nombre debajo del álbum del mes correspondiente. Si no existe, lo crea con la descripción idéntica al álbum, sin normalizar.
  4. Escala la foto a 1024×768, de ser necesario, preservando todas las etiquetas EXIF, IPTC y XMP.
  5. Sube la foto escalada al álbum correspondiente, usando como nombre el nombre del archivo, como título la etiqueta Iptc.Application2.Caption, y como descripción una vez más el álbum.

Todo esto es automático, y además el programa es suficientemente listo como para comprobar la existencia de cada álbum exactamente una vez; si ya comprobó que existe, guarda esa información para usarla en subsecuentes fotos. Además, si hay un error en la red lo detecta, y vuelve a subir la foto en ese caso. De los miles de fotos que subí, sólo me generó tres o cuatro duplicados, que además fue muy sencillo detectar. Mi programa incluso usa colorcitos para avisar qué está haciendo:

Mi programita en Python

Mi programita en Python

Las consecuencias de todo esto son varias: mi galería en línea tiene entonces una organización virtualmente idéntica a mis fotos en Shotwell (y por lo tanto en mi disco duro):

Mi galería en línea

Mi galería en línea

Pero además las fotos, durante todo este proceso, guardan la información siempre en ellas mismas; las tengo respaldadas (como ya he dicho) en varias máquinas en éste y otros sistemas solares, así que si algo terrible ocurriera con mi computadora y con mi galería en línea, no tengo nada de qué preocuparme: sólo copio mi respaldo, y puedo reconstruir mi colección en Shotwell casi de inmediato (sólo tengo que renombrar cada álbum, pero eso es muy rápido porque cada foto tiene una única etiqueta con el nombre de su álbum), y puedo reconstruir mi galería en línea de forma automática (aunque tendría que esperarme un rato a que acabaran de subirse las fotos).

Por supuesto, para que todo esto funcione las fotos en primer lugar deben tener la información dentro de ellas. Con la parte de mi colección que ya tenía organizada esto fue muy fácil, porque todo estaba en la base de datos SQLite de F-Spot. Con las fotos que no he organizado significa meterles el título (Iptc.Application2.Caption), y el álbum (Xmp.dc.subject). El álbum no me preocupa mucho, porque al cabo eso lo puedo hacer después de acomodarlas en directorios, y correr un script (que ya escribí), que lee el nombre del directorio (quitándole el prefijo numérico de ser necesario) y se lo pone a la foto.

El título es más desmadre, porque tengo que ponerlo foto por foto. Así que hice lo que cualquier programador que se llame así mismo uno haría: escribí un programa. Lo escribí en Vala (se me antojó después de ver el código de Shotwell), y de una vez le escribí el código necesario para que también pueda girar las fotos acostadas. Que de hecho no se giran, sólo se escribe una etiqueta EXIF que especifica que esa foto debe mostrarse girada.

Quick Photo

Quick Photo

El programa es bastante rápido; al dar Enter en el campo de texto, inmediatamente se guarda la información en la foto (que la imagen en sí no se modifica, sólo sus etiquetas), y se mueve a la siguiente. Lo único malo es que tengo que usar el ratón para girar la foto a la izquierda o derecha; tengo que programarle que lo pueda hacer desde el teclado, y entonces será casi perfecto para mis necesidades. Lo pienso liberar (junto con mi progamita en Python); a lo mejor a alguien le resulta útil.

Ahora sólo tengo que hacer lo que durante años estuve evitando: organizar las miles de fotos que no he organizado. Ya organicé (y subí) un par de meses de 2010; me falta el resto de ese año, el 2011 y el 2012. En 2012 no tomé casi fotos (estaba encerrado escribiendo la tesis, o quedándome sin novia, sin casa, sin dinero y sin trabajo), pero en 2011 tomé cientos de fotos en mi viaje por alrededor del mundo. Además, ya con esta infraestructura, supongo que debería también organizar las fotos de mi celular; varias lo valen, me parece, pero eso sí me va a tomar tiempo. Mientras tanto, tengo todo respaldado de forma redundantemente redundante, y cada vez que termino con un álbum nuevo (que generalmente se traduce a un día o dos de fotos), vuelvo a respaldar todos los cambios.

Mi punto con todo este impresionante choro, es que cuando creé mi galería en línea, el programa me pidió que le pusiera un nombre a la galería. Yo, falto como siempre de imaginación, le puse “Fotos de Canek”; así se sigue llamando hasta estos días. Pero después de ponerle el nombre, me pidió que lo describiera, y en ese momento (hace casi ocho años), sin pensarlo demasiado le puse “La vida a través de una cámara digital”.

Respaldando, reparando y reorganizando todas las fotos que he tomado desde abril de 2005, me di cuenta de que no pude haber elegido una mejor descripción para mi galería en línea: de verdad refleja mucho (aunque no todo, y muchas veces ni siquiera lo más importante) de lo que ha sido mi vida en estos años, que cubren básicamente mi maestría y doctorado hasta ahora. Los invito a que ustedes también le echen un ojo, si así lo desean, a mi renovada y mejorada galería en línea; pocas cosas me enorgullecen y alegran más que poder compartir las imágenes que capturan los varios momentos significativos que he tenido, y los viajes que he realizado.

Ahora que volví a revivir casi todos los momentos que fueron capturados con mi camarita Sony, no pude sino llegar a dos conclusiones: la primera, que soy increíblemente afortunado. Y la segunda: que me la he pasado poca madre en estos años. Incluso considerando los momentos amargos, las experiencias tristes, y las inevitables heridas del corazón, en retrospectiva para mí el balance es claro: lo bueno supera con creces, y por mucho, a lo malo. Me he divertido como enano en todo este tiempo.

Y lo bailado, nadie me lo quita.

Los 500 GB

Hace unos meses se me murió un disco duro de 500 GB. No había nada ahí que no tuviera de forma redundante en otra máquina (y en algunos casos en varias otras máquinas), y lo que no estaba de forma redundante no era realmente importante para mí, o lo podía bajar de nuevo de Internet.

Todo, excepto varias decenas (si no centenas) de fotografías.

Mi galería en línea (la que acabo de actualizar, por cierto) la he tenido abandonada desde hace mucho tiempo. Regresé de mi primer viaje a Europa en marzo de 2009 con varias centenas de fotografías, y esto causó que me tardara mucho en catalogarlas y ordenarlas; mis subsecuentes viajes en 2010 agravaron la situación, y mi viaje monstruo de seis meses en el 2011 causaron que sencillamente dejara de pensar en que en algún momento de mi vida tenía que organizar mis fotos. Si lo hacía, terminaba tirado en el suelo en posición fetal respirando a través de una bolsa de papel.

Lo que terminó pasando fue que aventaba los nuevos archivos de mis fotos en varios directorios, y me olvidaba de ellos, sin jamás pensar “hey, tal vez sería buena idea respaldarlos”. Y entonces un día mi disco duro de 500 Gigabytes se murió, llevándose mi $HOME y otra partición que venía utilizando desde hacía años.

Esto ocurrió en las etapas finales de la escritura de mi tesis doctoral, así que no tuve tiempo de llorar por mis archivos perdidos. Mis documentos (incluyendo los de la tesis… y todos los demás) están triplemente respaldados en mi laptop, mi servidorcito Atom, y una máquina corriendo en una bóveda secreta debajo del océano Antártico, así que sólo cloné mis documentos en un $HOME vacío, y seguí escribiendo la tesis de doctorado.

No tiré el disco, porque pensé que tal vez un día existiría la posibilidad de recuperar la información en él.

Cuando acabé la tesis de doctorado, busqué en línea qué podía hacer para recuperar la información de un disco que estaba, aparentemente, morido. Debo resaltar que el disco duro en cuestión hacía los ruidos normales que hace un disco cuando se conecta y recibe poder, pero no aparecía en ningún lado: el BIOS de mi computadora no lo detectaba, y tampoco ocurría si lo conectaba por un cosito SATA→USB.

Internet no fue de gran ayuda, tal vez porque mi Google-fú no fue tampoco el mejor del mundo: básicamente pregunté “¿cómo le hago para revivir un disco duro morido?”. La respuesta en general fue que la mejor oportunidad (si no había click de la muerte), era agarrar el modelo del disco duro, y buscar en la red para comprar únicamente el PCB, el circuito (realmente una computadora chiquita) que está atornillado debajo. La idea era conseguir un reemplazo idéntico, y orar que con eso se pudiera recuperar la información.

Dado que ya entonces andaba corto de dinero, ni siquiera me pasó por la mente; por lo que leí había que pedir el famoso PCB a China, y nada más el envío iba a costar una lana, si es que acaso encontraba el modelo exacto que necesitaba, y sin ninguna garantía de que al final funcionara. Así que lo dejé pasar, y seguí con mi vida.

Que en este caso consistió en quedarme sin novia, sin casa, sin dinero y sin trabajo.

Cuando por fin me medio recuperé de eso, de las cosas que hice para distraerme fue actualizar y configurar propiamente mi media center. No recuerdo cuándo fue la última vez que platiqué de él, pero el año pasado lo cambié de Moovida a XBMC, que resultó ser de los programas más impresionantes en el mundo del software abierto que he visto en mi vida.

Organicé cuidadosamente mis series de televisión y mis películas, así como mi animé y mi música. Y cuando el polvo se asentó, resultó que el espacio libre en mis discos duros estaba peligrosamente bajo. Lo cual es obvio, porque había perdido 500 Gigabytes de espacio unos meses antes.

Cuando la situación se hizo insostenible, y al ver el ridículo precio al que han llegado los discos duros de 2 Terabytes, le hablé a Enrique y fuimos a que me comprara uno. El disco en sí mismo era necesario; pero además mi media center tenía un disco duro de 500 GB, y pensé que con suerte sería el mismo modelo del muerto, y por lo tanto que podría intercambiar los PCB de ambos, y milagrosamente recuperar mi información.

Así que el día que compré el disco de 2 Terabytes, agarré mi media center y lo abrí para cambiarle el disco. Cuando saqué el viejito, de inmediato vi que no me iba a servir para reparar el otro; son modelos distintos. Sin embargo, eso me hizo pensar que tal vez ahora no sería tan mala idea buscar el PCB del disco malo en la red, y eso hice.

Eso fue lo que debí hacer primero.

Resulta que ese modelo de disco duro en particular (de hecho de firmware) tiene un fallo muy conocido en la red: cuando un contador en el firmware llega a cierto número, el disco duro se apendeja y se queda ahí atorado en el estado “busy”, lo que hace que el problema sea mundialmente conocido como “Seagate BSY state”. Googléenlo, si quieren.

El punto es que en todos lados confirmaban que la información del disco estaba intacta, que era posible recuperarla, y de hecho que el mismo disco duro podía seguir siendo usado. El único problema es que necesitaba soldar un cable en especial para conectar el disco duro a un puerto serial, o canibalizar un cable de Nokia que ya tiene ese circuito integrado.

Hay una razón por la que no soy ingeniero: detesto estar peleándome con cablecitos. Por eso me ligué dos ingenieros el primer semestre de mi maestría para que con ellos pasara Arquitectura de Computadoras; yo me encargué de todo el software, y dejé que ellos se pelearan con el hardware.

Así que me puse a buscar el cable, un famoso Nokia CA-42, que para acabarla de amolar estoy seguro que tuve en algún momento de mi vida. En Amazon cuesta 7 dólares, pero no lo envían a México; en MercadoLibre está en 150 pesos, pero el envío cuesta otros 150. Así que me decidí ir al Centro a buscarlo. Lamentablemente sólo pude ir hasta hoy, porque estuve trabajando en un artículo hasta ayer en la noche. Y hoy es domingo, así que un montón de negocios estaban cerrados.

Por suerte lo encontré, pero vi con algo de temor que no era un cable de Nokia original, sino un clon chino, y ya había leído que a veces con cables no originales el procedimiento no servía. Como sea, lo compré (me costó 100 pesos), y regresé a mi casa. Voy a platicar muy por encima que es lo que tuve que hacer, pero pueden verlo a detalle aquí, aquí, o en el tubo, si les da hueva leer.

El disco duro seguía igual de muerto que meses atrás:

Disco duro muerto

Disco duro muerto

El PCB es lo que está debajo:

PCB

PCB

Lo quité:

PCB suelto

PCB suelto

Le puse un cartoncito encima de la cabeza, para que el PCB no se comunique con el disco duro, el mismo no responda, y entonces el firmware no entre en el estado ocupado:

Disco duro con cartón

Disco duro con cartón

Y volví a poner el PCB:

PCB con cartón

PCB con cartón

Después desmadré el cable de Nokia chafa, y lo conecté a un viejo cable para conectar CDs a tarjetas de sonido, para poder conectarlo fácilmente al disco duro:

Cable Nokia chafa

Cable Nokia chafa

Y finalmente prendí el disco duro, lo conecté por USB a la computadora con el cable, y comunicándome serialmente con él le mandé comandos para revivirlo. Pueden verlo a detalle en las páginas que ligo (y todo se puede hacer con Linux, sólo reemplacen Hyperterminal con minicom); la verdad no me importa mucho qué hicieran en particular los famosos comandos.

Lo que me importa es que funcionaron, y que recuperé mi disco duro.

Más importante aún es que toda la información en el mismo sigue ahí, y la estoy respaldando todita en este momento. Lo principal eran las fotos, pero de una vez estoy copiando todo, y a partir de ahora sí voy a mantener mis fotografías también triplemente respaldadas.

Y encima ahora tengo un disco duro extra de 500 GB. Y un cable Nokia chafa para hacer todo de nuevo, si acaso es necesario.

Y el claro ganador es C

Ya no seguí con mis comparaciones entre C y Java porque, de verdad, he andado demasiado ocupado, con cosas académicas, de trabajo y personales. Pero hoy por fin terminé de escribir las pruebas unitarias para las estructuras de datos que estamos viendo en mi curso de ídem, y como la última que hemos visto fueron árboles rojo-negros, decidí echarle un ojo a mis pruebas.

Para los que no lo sepan, los árboles rojo-negros son árboles binarios autobalanceados, lo que en su caso particular quiere decir que un camino simple (sin regresarse nunca) de cualquier nodo a cualquiera de sus hojas siempre tiene el mismo número de nodos negros. Eso se traduce (por una propiedad de los árboles rojo-negros que dice que un nodo rojo siempre tiene dos hijos negros) a que la diferencia más grande de longitudes entre ramas es que una tenga k nodos, y la otra 2k (una rama con puros nodos negros, otra con negro, rojo, negro, rojo, etc.) La estructura permite agregar y eliminar elementos con complejidad en tiempo acotada por la altura del árbol; que siempre esté balanceado garantiza que dicha complejidad es siempre O(log n). Los árboles rojo-negros son una estructura de datos utilizada en todos lados por (como veremos ahorita) su espectacular desempeño; en particular, el kernel de Linux incluye una implementación desde mi punto de vista preciosa y humilladoramente elegante; la pueden checar en /usr/src/linux/lib/rbtree.c.

No voy a poner mi código para agregar o eliminar elementos a árboles rojo-negros; no sólo porque mis alumnos aún no entregan su práctica, sino además porque es demasiado engorroso. No es ciencia de cohetes, pero sí hay suficientes casos como para que uno tenga que ir haciendo dibujitos en papel para no perderse en qué punto del algoritmo nos hayamos. Como sea, terminé de escribir el código en Java y C como siempre, y corrí unas pruebas con un millón de elementos (me pueden pedir el código, si quieren).

Ambas implementaciones le ganan por mucho a MergeSort; me imagino que algo tendrá que ver el uso de memoria (MergeSort crea el equivalente a log n listas con n elementos cada una durante la ejecución del algoritmo, mientras que los árboles rojo-negros usan O(1) de memoria al agregar). Ambas son básicamente idénticas, incluyendo que usan recursión en el paso interesante: es recursión de cola, por lo que sencillamente lo pude haber reescrito iterativamente; pero como les digo el algoritmo ya es lo suficientemente engorroso como para que lo complicara aún más con un acumulador. La única diferencia discernible en el uso de cada versión, es que guardo la raíz cada vez que agrego en un elemento en C; tengo que hacerlo para no tener que andarla persiguiendo cada vez. En Java, al usar el diseño orientado a objetos, siempre tengo una variable de clase para la raíz.

Con Java, el algoritmo tarda 0.777676317 segundos (en promedio) en agregar 1,000,000 (un millón) de elementos. C sin optimizaciones tarda 0.376824469 segundos; con optimizaciones tarda 0.183850452 segundos. Por supuesto ambas versiones son genéricas; la de Java propiamente usando genéricos, la de C usando void* como tipo de dato, y pasando una apuntador a función para hacer comparaciones. Con 10,000,000 (diez millones) de elementos la diferencia es todavía más abismal; Java tarda 20.266719661 segundos, mientras que la versión en C tarda 1.881134884 segundos; pero esto ya no me extraña, dado que como ya había visto la última vez, con 10,000,000 de elementos, Java no puede evitar no utilizar el swap.

No me queda claro por qué C gana; dado que MergeSort también es recursiva, y ahí Java le ganaba a C, hubiera esperado que en el caso de los árboles rojo-negros pasara lo mismo. Lo que sí es que el desempeño de la estructura de datos es espectacular, y a mí me parece de las estructuras más bonitas y poderosas que existen. Por supuesto los diccionarios (¿alguien sabe una mejor traducción para hash table?) también son muy padres; pero siempre está el hecho de que en el peor de los casos el buscar y el eliminar tardan O(n). Y como mis experimentos con QuickSort me recordaron hace unas semanas, el peor caso (o uno suficientemente malo) siempre anda asomándose detrás de las esquinas.

Más sobre carreritas entre Java y C

Total que Omar me pidió mi código para hacer él mismo pruebas, lo que me obligó a limpiarlo un poquito. Entre las cosas que hice al limpiar mi código, fue cambiar cómo llenaba los arreglos y listas; originalmente los estaba llenando así (en Java):

import java.util.Random;
// ...
    Random r = new Random();
    // ...
    int n = r.nextInt() % 100;

Y así en C:

#include < stdlib.h >
// ...
    srand((unsigned int)time(NULL));
    // ...
    int n = rand() % 100;

El cambio que hice fue el reemplazar el número mágico 100 con N en el módulo al generador de números pseudo aleatorios, donde N es el número de elementos. Esto cambió radicalmente los resultados; para ponerlo en perspectiva, aquí están los resultados en C de una corrida (suelen ser todos muy similares) con módulo 100 (incluí el qsort de glibc a sugerencia de otro lector):

Tiempo QuickSort (normal): 46.125371409 segundos
Tiempo QuickSort (int): 6.318009789 segundos
Tiempo QuickSort (memcpy): 29.476040174 segundos
Tiempo QuickSort (long): 4.455134060 segundos
Tiempo QuickSort (glibc qsort): 0.182938334 segundos
Tiempo MergeSort (lista): 5.097989382 segundos
Tiempo MergeSort (dlista): 3.018067951 segundos

En Java los números son:

Tiempo QuickSort (int): 2.231362337 segundos
Tiempo QuickSort (genéricos): 35.452854731 segundos
Tiempo MergeSort: 2.599635738 segundos

Haciendo módulo N, los resultados son, en C:

Tiempo QuickSort (normal): 0.558278904 segundos
Tiempo QuickSort (int): 0.117254171 segundos
Tiempo QuickSort (memcpy): 0.279380050 segundos
Tiempo QuickSort (long): 0.121708671 segundos
Tiempo QuickSort (glibc qsort): 0.220501083 segundos
Tiempo MergeSort (lista): 5.311177622 segundos
Tiempo MergeSort (dlista): 3.196143267 segundos

Y en Java:

Tiempo QuickSort (int): 0.172914364 segundos
Tiempo QuickSort (genéricos): 0.578500354 segundos
Tiempo MergeSort: 2.15927644 segundos

Al inicio me súper saqué de onda, pero no tardé en encontrar la respuesta. Si N=1000000 (un millón), y cada entero en mi arreglo está entre 0 y 99 (inclusive), eso quiere decir que en promedio cada elemento del arreglo está repetido 10,000 veces, lo que hace que la probabilidad de encontrar un pivote malo (el mínimo o máximo del arreglo) sea mucho mayor que si estuvieran mejor distribuidos los valores. Es por ello que cuando cambio a hacer módulo N, todas mis versiones del algoritmo mejoran, algunas por varios órdenes de magnitud; en general el pivote es un buen pivote. Esta no es toda la historia, por supuesto; de ser así sólo tendría que elegir un pivote aleatorio y todo funcionaría más rápido, y como expliqué en la entrada anterior, en mis pruebas usar un pivote aleatorio no mejora sustancialmente el desempeño del algoritmo. Me parece que teniendo tantos elementos y tan poquitos valores (comparativamente) para inicializarlos, sencillamente ocurre muchas veces que un subarreglo tiene muchos elementos iguales, y entonces incluso un pivote aleatorio tiene mucha probabilidad de ser el mínimo o el máximo.

Sin embargo, esto no parece molestarle a qsort de glibc; es endiabladamente rápido. Incluso en mi versión módulo 100 corre en menos de medio segundo, y de hecho es la única implementación del algoritmo que lo hace. Como le comentaba al lector que me recomendó probar con qsort, la complejidad del mismo es más de un orden de magnitud mayor que mis versiones: qsort son unas 250 líneas de código no muy fácil de leer a comparación de 22 líneas en cada una de mis versiones. La complejidad radica en primer lugar en cómo encuentra el pivote: selecciona la mediana entre el primer, medio y último elementos del subarreglo (ordenándolos de paso); y en segundo lugar en que no hace recursión: utilizando una pilita se ahorra el estar gastando registros de activación, y mete todo dentro de un while.

No sé si sea eso lo que lo hace el ganador indiscutible de las distintas implementaciones; lo que sí sé es que glibc lo comenzaron a escribir a inicios de los ochentas, y que en estos treinta años le han metido todas las optimizaciones que se les han podido ocurrir. De cualquier forma, estoy ahora más contento con mis implementaciones en C: si el arreglo tiene sus valores bien distribuidos, cada una de mis versiones en C le gana a su equivalente en Java, y de hecho mi mejor versión genérica en C (que tiene exactamente la misma firma de qsort, por cierto) es sólo 0.05 segundos más lenta. Que no está nada mal, si me permiten decirlo.

De cualquier forma, los arreglos con muchos elementos repetidos son una entrada válida de QuickSort, así que Java sigue siendo bastante bueno en su versión para enteros (por cierto, mi versión genérica era desde el inicio mejor que la de Java; lo que pasa es que en Java el módulo incluye valores negativos, y entonces Java tenía el doble de valores distintos que la versión de C), y no terriblemente lento en comparación en las otras versiones. Para cosas genéricas C le puede ganar fácilmente; pero usando enteros nada más, Java le gana de calle a mi versión en C para arreglos con muchos elementos repetidos. Y de hecho implementando una versión iterativa de QuickSort, no dudo que Java le diera una buena pelea a qsort (un torito, para el que quiera animarse).

La otra conclusión importante es ver la enorme diferencia que puede significar el tipo de entrada para QuickSort; es por eso que Java utiliza MergeSort en general en sus algoritmos de ordenamientos. Es más lento en el mejor caso de ambos, sin duda; pero siempre es O(n log n). Además es estable (no cambia el orden de dos elementos iguales), lo que también está padre.

Dado que ya limpié el código gracias a Omar, lo pueden bajar de aquí. Dado que son implementaciones de una estructura y dos algoritmos que probablemente sean los más conocidos en el mundo, ni siquiera le pongo licencia a los archivos.

Carreritas entre Java y C

Estoy enseñando Introducción a Ciencias de la Computación II (mejor conocida como ICC-2) por primera vez en mi vida, en gran medida por un ligero error administrativo. La verdad es que me estoy divirtiendo como enano (al parecer, tristemente disfruto yo más el curso que mis alumnos), y entre las cosas divertidas que decidí hacer fue el darle a mis alumnos la oportunidad de hacer las prácticas en C (en lugar de Java) por un punto extra durante el curso, o medio si hacen al menos la mitad. Desafortunadamente, ninguno de mis alumnos me ha entregado una práctica escrita en C.

Como sea, para poder dejarles las prácticas en C a mis alumnos, primero tengo que hacerlas yo, y eso es en gran medida la razón de que me esté divirtiendo tanto. Por supuesto también hago las prácticas en Java; como ICC-2 es en gran parte estructuras de datos, esto también significa ver las características novedosas de Java, como son iteradores y genéricos. Lo cual también es muy divertido; especialmente cuando puedo comparar los dos lenguajes en cosas como velocidad de ejecución.

Como es necesario siempre que uno ve arreglos y listas, les dejé a mis estudiantes que programaran QuickSort y MergeSort. Yo recuerdo que como estudiante tuve que programar esos algoritmos al menos tres veces: la primera en ICC-2, la segunda en Análisis de Algoritmos, y ahora sí que como dice la canción, la tercera por placer. También recuerdo claramente que QuickSort me parecía el mejor de ambos algoritmos; la inocencia de tener veinte años, supongo.

Total que implementé ambos algoritmos en C y en Java, y me llevé una sorpresa con los resultados. Voy a relatar lo que resultó de investigar porqué las diferencias en velocidades, que la verdad yo no termino de entender.

Aquí está QuickSort en Java:

public static void swap(T[] a, int i, int j) {
    if (i == j)
        return;
    T t = a[j];
    a[j] = a[i];
    a[i] = t;
}

public static < T extends Comparable < T > >
                 void quickSort(T[] a) {
    quickSort(a, 0, a.length-1);
}

private static < T extends Comparable < T > >;
                  void quickSort(T[] a, int ini, int fin) {
    if (fin - ini < 1)
        return;
    int i = ini + 1, j = fin;
    while (i < j)
        if (a[i].compareTo(a[ini]) > 0 &&
            a[j].compareTo(a[ini]) < = 0)
            swap(a, i++, j--);
	else if (a[i].compareTo(a[ini]) <= 0)
	    i++;
	else
	    j--;
    if (a[i].compareTo(a[ini]) > 0)
        i--;
    swap(a, ini, i);
    quickSort(a, ini, i-1);
    quickSort(a, i+1, fin);
}

Y aquí está en C:

inline static void
swap(void** a, int i, int j)
{
	if (i == j)
		return;
	void* t = a[i];
	a[i] = a[j];
	a[j] = t;
}

void
quicksort(void** a, int n, func_compara f)
{
	quicksort_aux(a, 0, n-1, f);
}

static void
quicksort_aux(void** a, int ini, int fin, func_compara f) {
	if (fin - ini < 1)
	    return;
	int i = ini + 1, j = fin;
	while (i < j)
		if (f(a[i], a[ini]) > 0 &&
                    f(a[j], a[ini]) < = 0)
			swap(a, i++, j--);
		else if (f(a[i], a[ini]) <= 0)
			i++;
		else
			j--;
	if (f(a[i], a[ini]) > 0)
		i--;
	swap(a, ini, i);
	quicksort_aux(a, ini, i-1, f);
	quicksort_aux(a, i+1, fin, f);
}

(En mi blog el código aparece bonito con destacamiento de sintaxis; no sé cómo aparecerá en RSS, pero dudo que bonito.)

Con el código así, la versión en Java necesita 33.4 segundos (en promedio en mi máquina) para ordenar un arreglo de un millón (1,000,000) de elementos aleatorios. Sin optimizaciones, la versión en C tarda 114.7 segundos; lo cual es una diferencia brutal, si me permiten decirlo. Con la mejor optimización (-O3; el resultado es idéntico a -Ofast), esta velocidad baja a 44.85 segundos; mucho mejor, pero de cualquier forma más lento que con Java.

La versión en C utiliza void** como tipo del arreglo, y recibe un apuntador a función f justamente para emular los genéricos de Java; la idea es que el QuickSort de C pueda ordenar arreglos de cualquier tipo de elemento. Nada más por completez, incluyo la definición del tipo func_compara, así como la implementación usada para estas pruebas:

typedef int  (*func_compara)      (const void*   a,
				   const void*   b);

int
compara_enteros(const void* a, const void* b) 
{
	int aa = *((int*)a);
	int bb = *((int*)b);
	return aa - bb;
}

Mi primera impresión fue que estos “genéricos” en C (altamente basados en la biblioteca GLib) le estaban dando en la madre a la velocidad de ejecución de mi implementación en C. El andar siguiendo los apuntadores, sacar el valor de las referencias en compara_enteros, y los castings probablemente eran la razón (pensaba yo) de que mi versión en C fuera (ligeramente) más lenta que la de Java. Así que hice trampa y volví a implementar QuickSort, pero esta vez nada más para enteros:

inline static void
swap_int(int* a, int i, int j)
{
	if (i == j)
		return;
	int t = a[i];
	a[i] = a[j];
	a[j] = t;
}

void
quicksort_int(int* a, int n)
{
	quicksort_int_aux(a, 0, n-1);
}

static void
quicksort_int_aux(int* a, int ini, int fin) {
	if (fin - ini < 1)
	    return;
	int i = ini + 1, j = fin;
	while (i < j)
		if (a[i] > a[ini] &&
                    a[j] < = a[ini])
			swap_int(a, i++, j--);
		else if (a[i] <= a[ini])
			i++;
		else
			j--;
	if (a[i] > a[ini])
		i--;
	swap_int(a, ini, i);
	quicksort_int_aux(a, ini, i-1);
	quicksort_int_aux(a, i+1, fin);
}

No muy sorprendentemente, esta versión le partió completamente su madre a la de Java: tarda en promedio 6.35 segundos. Hago notar que los elementos del arreglo son generados aleatoriamente; por lo que el escoger un pivote aleatorio entre ini y fin no serviría (en teoría) de nada. Ciertamente no marcó ninguna diferencia en mis pruebas.

Aunque esta versión es bastante rápida, estaba haciéndo muchísima trampa. De nada (o muy poco) me sirve un QuickSort rapidísimo, si voy a tener que reimplementarlo cada vez que cambie el tipo de mis arreglos. Así que me puse a pensar cómo mejorar una versión “genérica” en C. La respuesta es que sí se puede, pero es bastante feo desde mi punto de vista.

La idea es sencillamente utilizar aritmética de apuntadores, y al intercambiar elementos el copiarlos usando la memoria:

inline static void
swap_memcpy(void* a, int i, int j, size_t s, void* t)
{
	if (i == j)
		return;
	memcpy(t, a+(i * s), s);
	memcpy(a+(i * s), a+(j * s), s);
	memcpy(a+(j * s), t, s);
}

void
quicksort_memcpy(void* a, int n, size_t s, func_compara f)
{
	void* t = malloc(s);
	quicksort_memcpy_aux(a, 0, n-1, f, s, t);
	free(t);
}

static void
quicksort_memcpy_aux(void* a, int ini, int fin,
                    func_compara f, size_t s, void* t) {
	if (fin - ini < 1)
	    return;
	int i = ini + 1, j = fin;
	while (i < j)
		if (f(a+(i*s), a+(ini*s)) > 0 &&
                    f(a+(j*s), a+(ini*s)) < = 0)
			swap_memcpy(a, i++, j--, s, t);
		else if (f(a+(i*s), a+(ini*s)) <= 0)
			i++;
		else
			j--;
	if (f(a+(i*s), a+(ini*s)) > 0)
		i--;
	swap_memcpy(a, ini, i, s, t);
	quicksort_memcpy_aux(a, ini, i-1, f, s, t);
	quicksort_memcpy_aux(a, i+1, fin, f, s, t);
}

Esta versión es superior a la de void** ya que puedo pasarle un arreglo de tipo int* directamente, y funciona sin problema; la versión void** necesita por fuerza que le pase un arreglo de apuntadores al tipo que me interesa; en otras palabras, tengo que pasarle un int**, y además tengo que hacerle cast a void**. Además de esto (que no es poco), es más rápido que la primera versión en C, y más rápido que la versión en Java. No por mucho, pero más rápido: tarda 27.5 segundos con un millón de elementos.

Por lo demás, está bastante fea; necesito por fuerza el tamaño del tipo que me interesa ordenar (porque el arreglo lo veo como un chorizo enorme de bytes), y por lo mismo para intercambiar elementos del arreglo debo utilizar memcpy, además de que cargo por todas partes un pedazo de memoria t para guardar el valor temporal durante el intercambio; la alternativa hubiera sido usar variables globales (the horror!), o solicitar y liberar memoria en cada intercambio de variables.

Hasta aquí estaba más o menos satisfecho: ya tenía una versión “genérica” en C que era más rápida que la de Java (aunque desde mi punto de vista la solución sea bastante fea), pero entonces se me ocurrió que estaba siendo muy injusto: si hice una versión tramposa para C (la que sólo sirve para enteros), debería hacer una versión tramposa para Java también. Así que eso hice:

public static void swap(int[] a, int i, int j) {
    if (i == j)
        return;
    int t = a[j];
    a[j] = a[i];
    a[i] = t;
}

public static void quickSort(int[] a) {
    quickSort(a, 0, a.length-1);
}

private static void quickSort(int[] a, int ini, int fin) {
    if (fin - ini < 1)
        return;
    int i = ini + 1, j = fin;
    while (i < j)
        if (a[i] > a[ini] &&
            a[j] < = a[ini])
            swap(a, i++, j--);
        else if (a[i] <= a[ini])
            i++;
        else
            j--;
    if (a[i] > a[ini])
        i--;
    swap(a, ini, i);
    quickSort(a, ini, i-1);
    quickSort(a, i+1, fin);
}

Esta versión tarda 2.26 segundos en ordenar un arreglo de un millón de elementos, lo que la hace casi tres veces más rápida que la versión tramposa de C. ¿Por qué ocurre esto? Sinceramente, no tengo idea; lo único que se me ocurre es que con 1,000,000 recursiones, el compilador Just-In-Time (JIT) de Java alcanza a optimizar algo durante la ejecución del programa que la versión en C no puede. Yo no veo otra alterantiva; pero me encantaría oír teorías.

Sólo un pequeño dato para terminar con QuickSort; hice otra versión tramposa en C para el tipo long, y ésta corre en 4.35 segundos, lo que la sigue haciendo más lenta que la de Java, pero más rápida que la de enteros (int) en C. ¿A lo mejor porque mi máquina es arquitectura AMD64? Una vez más, no tengo idea; pero sí me gustaría saber qué carajo hace la JVM para ser tan rápida.

Los enigmas no terminaron ahí, porque también implementé MergeSort en Java y C. Primero les enseño mis estructuras de datos en ambos lenguajes; estas son mis listas en Java:

public class Lista< T > implements Iterable< T > {
    protected class Nodo< T > {
	public T elemento;
	public Nodo< T > siguiente;
	public Nodo< T > anterior;
    }
    protected Nodo< T > cabeza;
    protected Nodo< T > rabo;
    public void agregaFinal(T elemento) { ... }
    public void agregaInicio(T elemento) { ... }
    ...
}

Ignoren el Iterable; es sólo para poder recorrer la lista con el foreach de Java. Las listas en C siguen el modelo estructurado en lugar del orientado objetos; por lo tanto en C lidiamos con los nodos directamente (mientras en Java siempre están ocultos al usuario):

struct lista 
{
	void* elemento;
	struct lista* siguiente;
	struct lista* anterior;
};

struct lista* lista_agrega_final  (struct lista* lista,
				   void*         elemento);
struct lista* lista_agrega_inicio (struct lista* lista,
				   void*         elemento);

Por su puesto para su práctica mis alumnos tuvieron que implementar más cosas; pero nada de eso es relevante para lo que discuto aquí. Mi implementación de MergeSort en Java (para estas listas) fue la siguiente:

private static < T extends Comparable< T >> Lista< T >
    merge(Lista< T > li, Lista< T > ld) {
    Lista< T > l = new Lista< T >();
    Lista< T >.Nodo< T > nli = li.cabeza;
    Lista< T >.Nodo< T > nld = ld.cabeza;
    while (nli != null && nld != null) {
        if (nli.elemento.compareTo(nld.elemento) < 0) {
            l.agregaFinal(nli.elemento);
            nli = nli.siguiente;
        } else {
            l.agregaFinal(nld.elemento);
            nld = nld.siguiente;
        }
    }
    while (nli != null) {
        l.agregaFinal(nli.elemento);
        nli = nli.siguiente;
    }
    while (nld != null) {
        l.agregaFinal(nld.elemento);
        nld = nld.siguiente;
    }
    return l;
}

public static < T extends Comparable< T >> Lista< T >
    mergeSort(Lista< T > l) {
    int n = l.longitud();
    if (n == 1)
        return l;
    Lista< T > li = new Lista< T >();
    Lista< T > ld = new Lista< T >();
    int i = 0;
    Iterator< T > iterador = l.iterator();
    while (i++ < n/2)
        li.agregaFinal(iterador.next());
    while (i++ <= n)
        ld.agregaFinal(iterador.next());
	
    li = mergeSort(li);
    ld = mergeSort(ld);
    return merge(li, ld);
}

La versión en C es la que sigue:

static struct lista*
merge(struct lista* li, struct lista* ld, func_compara f) 
{
	struct lista* l = NULL;
	struct lista* ii = li;
	struct lista* id = ld;

	while (ii != NULL && id != NULL) {
		if (f(ii->elemento, id->elemento) < 0) {
			l = lista_agrega_inicio(l, ii->elemento);
			ii = ii->siguiente;
		} else {
			l = lista_agrega_inicio(l, id->elemento);
			id = id->siguiente;
		}
	}
	while (ii != NULL) {
		l = lista_agrega_inicio(l, ii->elemento);
		ii = ii->siguiente;
	}
	while (id != NULL) {
		l = lista_agrega_inicio(l, id->elemento);
		id = id->siguiente;
	}

	struct lista* tmp = lista_reversa(l);
	lista_libera(l);
	return tmp;
}

struct lista*
mergesort(struct lista* l, func_compara f)
{
	int n = lista_longitud(l);
	if (n == 1) {
		struct lista* uno =
                        lista_agrega_inicio(NULL, l->elemento);
		return uno;
	}
	struct lista* li = NULL;
	struct lista* ld = NULL;
	int i = 0;
	struct lista* tmp = l;
	while (i++ < n/2) {
		li = lista_agrega_inicio(li, tmp->elemento);
		tmp = tmp->siguiente;
	}
	while (i++ < = n) {
		ld = lista_agrega_inicio(ld, tmp->elemento);
		tmp = tmp->siguiente;
	}

	tmp = lista_reversa(li);
	lista_libera(li);
	li = tmp;

	tmp = lista_reversa(ld);
	lista_libera(ld);
	ld = tmp;

	tmp = ordenamientos_mergesort(li, f);
	lista_libera(li);
	li = tmp;

	tmp = ordenamientos_mergesort(ld, f);
	lista_libera(ld);
	ld = tmp;

	tmp = merge(li, ld, f);
	lista_libera(li);
	lista_libera(ld);
	return tmp;
}

Dado que una "lista" es realmente un nodo de la lista (siguiendo el modelo utilizado por GLib), no tengo guardado en nigún lado el rabo de la lista; por eso agrego elementos al inicio, y cuando termino la volteo. Hice mis pruebas de nuevo con 1,000,000 elementos, y lo primero que me sorprendió fue que fuera tan rápido en comparación con QuickSort; yo recordaba que cuando los implementé en mi carrera, la diferencia no era tanta. A lo mejor ahora programo mejor.

La versión en Java tarda (en promedio) 2.25 segundos; la versión en C 4.8, más del doble. Esta vez ya estaba preparado y no me sorprendió tanto, y de inmediato pensé que una obvia optimización es cargar el rabo de cada lista, y así poder agregar elementos al final en tiempo constante, sin tener que preocuparme de voltearla después. Para eso creé esta estructura de datos:

struct dlista {
	struct lista* cabeza;
	struct lista* rabo;
	int longitud;
};
void dlista_agrega_final(struct dlista* dl, void* elemento);
void dlista_agrega_inicio(struct dlista* dl, void* elemento);

Pude entonces simplificar mi versión de MergeSort:

static struct dlista*
merge(struct dlista* dli, struct dlista* dld, func_compara f) 
{
	struct dlista* dl = dlista_nueva();
	struct lista* ii = dli->cabeza;
	struct lista* id = dld->cabeza;

	while (ii != NULL && id != NULL) {
		if (f(ii->elemento, id->elemento) < 0) {
			dlista_agrega_final(dl, ii->elemento);
			ii = ii->siguiente;
		} else {
			dlista_agrega_final(dl, id->elemento);
			id = id->siguiente;
		}
	}
	while (ii != NULL) {
		dlista_agrega_final(dl, ii->elemento);
		ii = ii->siguiente;
	}
	while (id != NULL) {
		dlista_agrega_final(dl, id->elemento);
		id = id->siguiente;
	}

	return dl;
}

struct dlista*
mergesort(struct dlista* dl, func_compara f)
{
	int n = dl->longitud;
	if (n == 1) {
		struct dlista* uno = dlista_nueva();
		dlista_agrega_final(uno, dl->cabeza->elemento);
		return uno;
	}
	struct dlista* dli = dlista_nueva();
	struct dlista* dld = dlista_nueva();
	int i = 0;
	struct lista* tmp = dl->cabeza;
	while (i++ < n/2) {
		dlista_agrega_final(dli, tmp->elemento);
		tmp = tmp->siguiente;
	}
	while (i++ < = n) {
		dlista_agrega_final(dld, tmp->elemento);
		tmp = tmp->siguiente;
	}

	struct dlista* tmp2;
	tmp2 = mergesort(dli, f);
	dlista_libera(dli);
	dli = tmp2;

	tmp2 = mergesort(dld, f);
	dlista_libera(dld);
	dld = tmp2;

	tmp2 = merge(dli, dld, f);
	dlista_libera(dli);
	dlista_libera(dld);
	return tmp2;
}

Esta nueva versión corre en 2.72 segundos; mucho más cerca a la versión de Java, pero todavía más lenta. Lo único extra que se me ocurrió que podía hacer era eliminar el manejo de memoria; pensando que tal vez Java es más rápido (en este caso) porque puede diferir el liberar memoria hasta después de correr el algoritmo. Así que quité las llamadas a la función dlista_libera tratando de emular como sería tener recolector de basura, y por supuesto el algoritmo corrió ahora más lento: 2.92 segundos. ¿A lo mejor con 1,000,000 de elementos consigo forzar que Linux pase memoria al swap? No tengo idea; pero la verdad lo dugo: tengo 4 Gb de memoria, y no vi que el foquito de mi disco duro se prendiera.

Todos estos resultados pueden atribuirse a errores del programador (dícese, yo), pero honestamente no creo estar haciendo nada obviamente mal. Mi teoría favorita (y de hecho la única) es que el compilador JIT de la JVM está haciendo algo de magia que el simple ensamblador optimizado de C no puede; lo cual sería una muesta feaciente e innegable de las ventajas que pueden tener los lenguajes de programación que compilan para una máquina virtual altamente optimizada. Sumado a que es mucho más sencillo programar todas estas estructuras de datos si uno no tiene que preocuparse de manejar la memoria, y además con la fuerte (y desde mi punto de vista muy bonita) tipificación de datos que ofrecen los genéricos en Java, la verdad no vería por qué alguien escogería C sobre Java para programar cosas que tengan que repetir una misma tarea cientos de miles de veces.

Por supuesto es un experimento sólo en mi máquina, y en estos días 1,000,000 de elementos me suena a que ya no es realmente tanto. Con 10,000,000 de elementos, la versión en C tardó 36.32 segundos, y la versión en Java tardó 41.98 segundos; además de que tuve que aumentarle el tamaño al heap de la máquina virtual de Java a 4 Gb. Si lo aumentaba a 2 Gb, tardaba 54.99 segundos; en ambos casos el foquito de mi disco duro se prendió. En uso de memoria, sin duda C sigue siendo mucho superior (al costo de que uno tiene que andar manejándola a mano).

De cualquier forma, es impresionante lo rápido que es Java hoy en día; cuando yo lo comencé a aprender (el siglo pasado), todavía muchísima gente se quejaba de lo lento que era. Ahora yo (que no tengo poca experiencia programando) no puedo hacer que una versión en C del mismo algoritmo le gane.

En nuestras vidas profesionales lo más probable es que mis alumnos y yo no tengamos que implementar ninguno de estos algoritmos nunca; lo más seguro es que ya existirá una versión suficientemente buena y suficientemente optimizada disponible, lo que haría medio inútil que la implementáramos de nuevo. Sin embargo, me parece importante que un computólogo las implemente aunque sea una vez en su vida, y entienda cómo funcionan y cómo podrían utilizar una versión personalizada para algún obscuro problema que se encuentren.

Y ahora tengo que trabajar en mis algoritmos para árboles binarios.

Extensiones

Isabel y yo tenemos cinco computadoras; dos laptops, mi computadora de escritorio, el servidorcito con procesador atom donde bajamos cosas, y mi media center. Si incluimos al PS3, son seis computadoras. Si incluimos el iPad de Isabel, son siete. Si incluimos nuestros teléfonos (que se puede argumentar que cuentan como computadoras), entonces son nueve.

Dado que seis de esos aparatos vinieron conmigo cuando me mudé con Isabel, no es de extrañar que el costo de la luz se haya incrementado por mucho (el horno de microondas, y mi tele de 46 pulgadas también deben influir). Eso implicó que empezáramos a hacer ciertos ajustes para disminuir nuestro consumo eléctrico.

El más obvio y sencillo fue el apagar algo cuando no se esté usando; lamentablemente es también el más incómodo en mi máquina de escritorio. Durante mi viaje de seis meses el año pasado, me acostumbré a trabajar en mi laptop, la cual nunca apago, a menos que le actualice el kernel o algo similar; en general sólo la suspendo, y entonces es básicamente instantáneo el regresar a trabajar: todas mis aplicaciones están abiertas tal cual antes de que la suspendiera.

Mi máquina de escritorio no tenía esto, y entonces sí es medio desesperante el prenderla y apagarla; no tanto por el tiempo que toma (es rápida al fin y al cabo), sino por el necesario esfuerzo de reconstruir mi estado de trabajo, dícese empezar mis aplicaciones y abrir mis documentos. Hoy por fin me puse a hacerle trutú a mi PC, y conseguí que suspendiera básicamente igual que mi laptop. La verdad me impresioné; nada más encontré la opción en el BIOS que lo permite, jaló de inmediato con Linux.

El problema es que suspender mi escritorio implica que tengo que dejarlo “prendido”. El CPU y discos duros están apagados, pero la máquina sigue consumiendo energía para mantener la memoria RAM activa, que es como funciona cuando uno la suspende. En la laptop no es tanto problema porque consume mucho menos electricidad que mi máquina de escritorio. Además, está el problema de que si se va la luz, pierdo todo (por lo anterior); en la laptop esto no pasa porque tiene batería.

Así que, después de años de no usarlo, investigué cómo hacer que mi máquina de escritorio hibernara. Hibernar es básicamente igual a suspender, sólo que los contenidos de la memoria van al disco duro en la partición del swap, y entonces la máquina se puede desconectar completamente. Al prenderse de nuevo, lo primero que hace Linux es restaurar los contenidos del swap a la memoria, y entonces todo regresa a como estaba. El proceso tarda un poco menos que prender la máquina de cero, y tiene la infinita ventaja de que el estado de la sesión de trabajo se preserva de modo perfecto; o en otras palabras, ya no tengo que iniciar mis aplicaciones ni reabrir mis documentos.

De todo ese choro no es esta entrada; la entrada es acerca de que GNOME no tiene una opción en su menú de estado para hibernar la máquina. Joder, con esfuerzos tiene una opción para apagarla; los genios de GNOME decidieron que todo mundo debería siempre suspender su computadora, y para poder apagarla uno tiene que presionar la tecla Alt para que la opción aparezca en el menú de estado.

Así que me fui al sitio de extensiones para el GNOME Shell, y busqué “hibernate”, y luego luego me salió la extensión Alternative Status Menu. Eso no tiene nada de sorprendente; lo ingenioso es que si visitan esa página con GNOME 3, aparece un botón para habilitar o deshabilitar la extensión. Así es, la pueden habilitar o deshabilitar desde el navegador (yo uso Chromium, pero al parecer funciona con Firefox y obviamente con Epiphany, el navegador de GNOME). Así que le hice click al botón, y de inmediato mi menú ganó la habilidad de apagar o hibernar sin tener que presionar teclas mágicas.

Menú de estado

Menú de estado

Por supuesto ya había leído al respecto, pero verlo funcionar sin que yo jamás hiciera nada sí me pareció sorprendente. No he jugado mucho con las extensiones del GNOME Shell (tengo sólo dos o tres), pero ésta es la primera que activo desde el sitio de extensiones, y la verdad estoy encantado con los resultados. De hecho, hasta me dan ganas de programar unas cuantas yo mismo.

Como sea, ya no tengo que preocuparme de restaurar el estado de mi escritorio cada vez que apago mi máquina; y si sólo dejo de trabajar un rato (porque voy a comer o algo así), puedo sólo suspenderla.

Y se quejaban de que GNOME 3 no ofrece suficientes opciones.

Habría que portarlo a mi Xperia Play

Hace unos días reportaron en Slashdot que el código fuente de Prince of Persia había sido liberado en Github.

Casi de inmediato, cloné el repositorio y le eché un ojo al código. Resulta ser una instancia sorprendentemente limpia de ensamblador para la Apple ][, que por supuesto yo jamás ni siquiera había visto; los únicos ensambladores que conozco son el de Sparc clásico, muy poquito de x86, y el que nos inventamos para nuestro procesador con números complejos en mi curso de Arquitectura de Computadoras en la maestría.

Dicho eso, todos los ensambladores se parecen (aunque los geeks de hardware pongan el grito en el cielo al oír eso y digan que RISC y CISC son fundamentalmente distintos), y he podido seguir el código, que es obviamente muy corto: 30,000 líneas de ensamblador (con comentarios incluidos). El juego corría de un sólo disco de 5¼ pulgadas, al fin y al cabo.

No creo poder hacer nada con el código, y menos ahorita con tan poquito tiempo, pero estaría padre que portaran el juego a mi Xperia Play (aunque puedo jugar la versión para NES en un emulador). A casi 25 años de que fue creado, sigue siendo uno de los mejores juegos que se han hecho, me parece.

Qué chido que lo liberaron.

Cómo has cambiado

Linux ya no es lo que era antes.

Isabel necesita escanear unas cosas y, por una serie de circunstancias que me niego a relatar, trajo un escáner de sus papás al departamento. Yo inmediatamente me puse a saltar como niño con juguete nuevo, pensando en toda la sana diversión que obtendría de estar tratando de echar a andar el (aparentemente) chafísima escáner USB.

Lo conecté, y funcionó de inmediato. Qué desilusión.

Cómo han cambiado las cosas en quince años; en 1997 tuve que recompilar mi kernel (por primera vez en mi vida) para que jalara mi tarjeta de sonido en Linux. Ni siquiera recuerdo para qué; dudo que yo supiera de la existencia de MP3s en ese entonces.

Ahora uno conecta las cosas, y funcionan. Qué aburrido.

Trucos con \LaTeX

Estoy escribiendo mi tesis de doctorado, y lo estoy haciendo en español porque al fin y al cabo los artículos sobre los que estará basada ya los escribí en inglés, y no le veo sentido a andarme rompiendo la cabeza de nuevo escribiendo en inglés cuando puedo hacerlo en español, y soy mucho mejor escritor en mi idioma natal.

Como sea, escribiendo \LaTeX en español de nuevo ha hecho que descubra (o redescubra) varios trucos interesantes. Lo primero es hacer que \LaTeX hable español, por supuesto, que se logra con un simple

\usepackage[spanish]{babel}

Lo siguiente es hacer que \LaTeX use UTF-8 para entender acentos, para así poder escribir á, y no \'a. Yo esperaría que ya todo mundo lo supiera, pero me he encontrado con varias personas que siguen usando el modo “tradicional”, que es por supuesto lento, propenso a errores, y en español hace que un documento sea ilegible. Para que \LaTeX use UTF-8, sólo se necesita un simple

\usepackage[utf8]{inputenc}

Con eso al 99% de la gente que escribe \LaTeX en español debería bastarle; para los neuróticos como yo, el que sigue está interesante. Con los dos paquetes de arriba \LaTeX ya genera un documento correcto usando español, pero si uno usa \textrm{PDF}\LaTeX (como yo, que ya le perdí la fe para siempre a PostScript), en el documento resultante no están sincronizados el texto dibujado en pantalla, y el texto subyaciente. Para que me entiendan, creen un documento \LaTeX con los dos paquetes que mencioné, compílenlo con \textrm{PDF}\LaTeX, y luego seleccionen una parte del documento con acentos. Debería pasarles algo así:

Texto seleccionado

Texto seleccionado

Eso no sólo se ve horrible; la búsqueda en el PDF deja de funcionar, y afecta también cosas como buscadores automáticos (como el de Google) que analizan los PDFs por el texto subyaciente, no por cómo se dibuje en la pantalla. Repararlo es bien sencillo:

\usepackage[T1]{fontenc}

Con este paquete, el PDF ya sincroniza el texto subyaciente con el dibujado en la pantalla, y todos los problemas que mencioné arriba se corrigen:

Texto seleccionado con fontenc

Texto seleccionado con fontenc

El siguiente truco está relacionado; para las tesis en la UNAM, la portada siempre tiene que seguir un cierto formato del que sencillamente no hay forma de escapar. La manera más sencilla (para mí) de cumplir con el requerimiento de la portada, fue hacerla en Inkscape, exportarla a PDF, e incluirla como página completa en mi documento con \includepdf, del paquete pdfpages. Ahora, todo el texto en la portada lo hice con \LaTeX dentro de Inkscape para que usara la misma fuente que el resto del documento, para esto usé la extensión textext de Inkscape que permite insertar la salida de \LaTeX como SVG dentro de un archivo de Inkscape (que también es SVG).

Todo esto funciona muy bien, pero como el texto de \LaTeX se inserta como SVG (dícese, líneas, curvas de Bézier, y cosas así), el PDF resultante no tiene texto subyaciente, y por lo tanto no es seleccionable, buscable, analizable, etc., porque de hecho no hay tal. Para arreglarlo es muy fácil; uno toma su documento en Inkscape:

Documento en Inkscape

Documento en Inkscape

Y le agrega texto de Inkscape, o sea, el texto que de hecho SVG sabe manejar:

Documento en Inkscape con texto

Documento en Inkscape con texto

Por supuesto, uno selecciona la fuente de Inkscape que mejor se acerque a la de \LaTeX aunque dado el cuidado que pone \LaTeX para dibujar texto, por mucho que se parezca la fuente de Inkscape no se verá igual (que es la razón por la cual uso texto de \LaTeX y no de Inkscape en primer lugar). Hecho esto, uno centra el texto de Inkscape sobre el de \LaTeX, para que estén casi uno encima del otro:

Documento en Inkscape con texto centrado

Documento en Inkscape con texto centrado

Y por último uno selecciona el texto de Inkscape, y lo hace invisible:

Documento en Inkscape con texto invisible

Documento en Inkscape con texto invisible

Y ya, con esto el texto subyaciente del PDF será el de Inkscape, y aunque no se verá idéntico al dibujado en el PDF, sí será seleccionable:

PDF seleccionable

PDF seleccionable

Por supuesto, cuando no se esté seleccionando, el texto de Inkscape será invisible, dejando únicamente visible (e imprimible) el texto bonito de \LaTeX. Ya que tuve mi portada lista, lo siguiente que pensé fue si valía la pena hacer las figuras de mi tesis seleccionables. Son chorroscientas, y además tendría que estar poniendo caracteres griegos la mayor parte del tiempo, y eso me dio mucha flojera. Sin embargo, hice una pequeña extensión para Inkscape que toma el texto de \LaTeX generado por textext, e inserta el código \LaTeX que generó el texto. Por ejemplo, la siguiente figura:

Figura

Figura

esto es lo que se ve cuando uno selecciona texto \LaTeX dentro de ella:

Figura seleccionable

Figura seleccionable

No es terriblemente útil, pero como ligué mi extensión en Inkscape a un atajo del teclado, es bastante fácil de hacer, y se ve mamón. Esos son todos los trucos que he aprendido (o vuelto a aprender); si encuentro otros luego los publico.

El código portátil

En Barcelona estuve trabajando con un investigador, Carlos Seara, y entre las varias cosas que hice fue el escribir un programita que utilizamos para ver ejemplos y cosas por el estilo. Desde hace años he venido juntando pequeños pedazos de código en Python que me permiten hacer programas de cosas geométricas (sólo en 2D) más o menos rápido; esos pedazos de código son lo único que queda de mi programa de geometría, geom, que lo tengo abandonado desde hace años. Como sea, el programita que hice en Barcelona está escrito en Python y, aunque funcional, ciertamente es muy sencillo:

RLayers en Linux, versión simple

RLayers en Linux, versión simple

Justo antes de salir de Toronto, Carlos me dijo que expondrá acerca de lo que trabajamos en noviembre, y que sería bueno que pudiera mostrar el programa durante su presentación. Dado que él usa Windows, me dijo que, si podía, le enviara una versión que jalara ahí.

Hay dos cosas que de verdad detesto en esta vida; una de ellas es Windows, y otra es tener que trabajar en Windows, así que no estaba muy emocionado con la idea, pero comencé a ver qué podía hacer. Yo tengo Windows Vista instalado en mi laptop (venía con la máquina), y para lo único que lo he utilizado en todo el tiempo que la he tenido es para actualizar mi teléfono Xperia Play (voy a creer que los idiotas de Sony-Ericsson no puedan hacer un programa para Linux); pero definitivamente no quería abandonar mi hermoso escritorio GNOME 3.2 para menearle cosas a Windows.

Definitivamente no: eso hubiera sido demasiado fácil.

En su lugar, hice lo que cualquier persona sensata no haría, e instalé Windows XP en una máquina virtual (afortunadamente los alumnos de la UNAM tenemos acceso a casi todas las versiones de Windows gratis). Hace unos años VMWare era la máquina virtual que todo el mundo usaba, pero desde hace algún tiempo le viene haciendo mucha competencia VirtualBox, que es de Oracle (antes Sun). VirtualBox tiene además la no despreciable ventaja de que es Software Libre.

Por supuesto VirtualBox viene con un interfaz gráfica que le permite a uno fácil y rápidamente preparar su máquina virtual dando unos cuantos botonazos del ratón. Pero, una vez más, eso sería demasiado fácil; la interfaz gráfica utiliza Qt, que yo no instalo en mis computadoras sino bajo la más absoluta emergencia. Por “suerte” VirtualBox se puede instalar sin interfaz gráfica, y configurar y echar a andar usando únicamente la línea de comandos.

Si les pudiera explicar toda la sana diversión que tuve haciéndolo. Como sea, eventualmente por fin tuve mi máquina virtual corriendo dentro de mi Linux:

Windows XP en VirtualBox

Windows XP en VirtualBox

Entonces ahí empezó la diversión en serio. Mi programa está escrito en Python, y utiliza básicamente Gtk+ y Cairo, con la bola de dependencias que esas dos bibliotecas se jalan. En teoría, un programa escrito en Python con Gtk+ y Cairo debería ser portátil a Windows sin muchas dificultades. En la práctica, uno tiene que estarle meneando a varias cosas en el código para que sea, bueno, portátil.

La verdad no fue tan difícil: uno básicamente instala Python para Windows, y luego instala un paquete cómodamente hecho de antemano que contiene Gtk+, sus bindings para Python, e ídem con la bola de dependencias necesarias. La cosa tiene el tino de llamarse all-in-one install. Con eso, mi aplicación ya corría en Windows (XP, al menos):

RLayers en Windows XP, versión simple

RLayers en Windows XP, versión simple

Aquí yo podría haber dicho “misión cumplida”, enviarle las instrucciones a Carlos, y dejar que sufriera lentamente instalando cosas en Windows (una experiencia miserable para cualquier persona). Pero no, decidí que iba a enviarle algo sencillo de instalar, y que de paso iba a mejorar el programita.

Así que de regreso en Linux me puse a mejorar el programita: le creé su interfaz gráfica en Glade, le puse menúes y barra de herramientas, le creé un formato para los archivos que utiliza, y pendejaditas de ese estilo. Ya a punto de acabar caí en cuenta de que probablemente correría en Español el programa, así que además le agregué soporte para gettext, para que pudiera hablar distintos idiomas:

RLayers en Linux, versión bonita

RLayers en Linux, versión bonita

Por supuesto, el chingado soporte para idiomas valió madre, porque en Windows gettext junto con PyGTK hace cosas muy extrañas. Al parecer hay forma de solucionarlo, pero involucran magia negra, así que me lavé las manos y forcé al programa a hablar inglés siempre. Como sea, ahora se veía bonito en Windows XP también:

RLayers en Windows XP, versión bonita

RLayers en Windows XP, versión bonita

Pero todo esto no eran más que manitas de gato: lo interesante era lograr que la chingadera fuera “fácilmente” instalable. Así que investigué y resultó que lo que había que hacer era utilizar un módulo de Python (en Windows) que “automágicamente” arma un ejecutable con todo lo necesario dentro para que el programa funcione, supuestamente ya sin necesidad de instalar nada más. El módulo es py2exe, y tiene una de las peores documentaciones que he visto, así que me tuve que basar en Google para saber exactamente qué tenía que hacer.

La cosa se volvió básicamente una pesadilla porque soy muy neurótico en muchas cosas: por ejemplo, el programa sigue siendo el mismo para Windows y para Linux, y el archivo de instalación (del cual se cuelga py2exe para hacer su magia) también funciona en los dos sistemas operativos. También la máquina virtual es bastante lenta (mi laptop no es muy poderosa para empezar), y eso causaba todavía más quebraderos de cabeza.

Pero por fin, después de muchas horas de dolor y sufrimiento, la cosa funcionó dentro de mi máquina virtual.

RLayers en Windows XP, de un ejecutable

RLayers en Windows XP, de un ejecutable

Ya teniendo todo esto hecho, lo único que faltaba era la prueba final: ver que funcionara en mi Windows Vista, el que no es virtual. Este paso es fundamental, porque el ejecutable de mi máquina virtual podía utilizar bibliotecas no incluidas por py2exe, y la única forma de comprobar esto es ejecutándolo en una máquina donde no se haya instalado Python, ni Gtk+, ni nada relacionado.

Así que cruzando los dedos reinicié a Windows, y por un milagro de esos que ocurren de vez en nunca, la maldita cosa funcionó sin problemas.

RLayers en Windows Vista, de un ejecutable

RLayers en Windows Vista, de un ejecutable

En mis planes originales estaba hacer un instalador, pero acabé realmente hasta la madre, y yo creo que Carlos no tendrá problemas en descomprimir un archivo Zip, y darle doble click a un ejecutable.

Ahora no quiero saber nada de Windows (ni de máquinas virtuales) en un buen rato.