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.

Apple sin cabeza

Ayer se murió Steve Jobs, como todo mundo sabe, y al inicio pensé en no escribir nada al respecto. Pero como todo mundo lo está haciendo y, además, todo mundo al parecer sigue cayendo en esta costumbre detestable de canonizar a alguien nada más pasa a pasar del estado vivo al estado morido, decidí escribir algunas palabras al respecto.

Steve Jobs no cambió mi vida. Al menos no directamente; nunca tuve una computadora de Apple, y de hecho nunca he tenido ningún producto de esa compañía, exceptuando un iPod Shuffle que compré en el 2005, y que nunca me gustó mucho realmente. Y dicho iPod nunca lo conecté a iTunes: lo usé con libgpod, cuando lo usaba, que con el paso del tiempo fue cada vez menos y menos veces. Y hablando de software, nunca tampoco usé software producido por Apple: a lo más algunas veces QuickTime, y siempre me produjo más dolores de cabeza que otras cosas.

Así que no, Jobs no cambió mi vida. Que el tipo “cambió” el mundo de la tecnología es innegable, por supuesto: pero lo cierto es que Jobs, hasta donde yo tengo entendido, jamás creó por sí mismo nada. Desde los inicios de Apple, cuando Steve Wozniak era el que le meneaba a la parte tecnológica, Jobs nunca fue realmente un creador: sabía muy bien cómo explotar las creaciones de otras personas, y sabía todavía mejor cómo presentarlas y vendérselas al mundo. Mucha gente habla de su “visión”, como si a Jobs realmente se le hubiera ocurrido algo nuevo alguna vez en su vida, cuando la triste verdad es que tomaba la tecnología existente y se la empacaba al usuario para que tuviera una experiencia sencilla, limpia, y sin duda alguna con mucho estilo. De creador no le reconozco nada a Jobs; de alguien que sabía muy bien cómo empaquetar tecnología de tal forma que fuera sencilla y divertida de usar, le reconozco todo.

Mucha forma, poco contenido.

A mí en particular nunca me atrajeron las Mac, los iPods (excepto mi Shuffle, que realmente me decepcionó), los iPod Touch, los iPhones o los iPads. El hardware, aunque sin duda alguna bonito, me pareció siempre excesivamente caro para lo que ofrecía. El software, aunque sin duda alguna bonito, era cerrado, así que de entrada jamás me llamó la atención.

Por supuesto, bien por aquellos que se orgasmeaban (y orgasmean) con los productos de Apple: cada quién lo suyo. Sólo estoy explicando por qué a mí nunca me llamaron la atención.

Y con Steve Jobs en particular nunca sentí (ni siento) nada negativo hacia él: jamás me hizo nada, que yo sepa. Sólo no le voy a asignar el epíteto de genio o visionario, cuando lo que fue realmente fue sólo un muy buen vendedor. Un muy buen merolico; tal vez el mejor de este inicio de siglo. Pero merolico al fin y al cabo.

No lamento su muerte más de lo que lamento la muerte de cualquier ser humano que, en general, no haya sido un hijo de la chingada (aunque al parecer eso era con sus empleados); y ciertamente tampoco me alegra, ni mucho menos. Pero tampoco creo que yo, en particular, ni el mundo, en general, tenga nada que agradecerle. El tipo hizo productos, y como buen merolico salió a la calle a venderlos, y fue endiabladamente exitoso al hacerlo. Tan exitoso que se pudrió de dinero haciéndolo (aunque al final ni todo el dinero del mundo lo salvó del cáncer, aunque con él se compró siete años más de vida).

No le dio (en el sentido de obsequiar) nada al mundo. Vender sí, vendió muchísimo, y fue inclemente con su competencia para que ellos no pudieran vender (vean la demanda de Apple contra Samsung en Alemania, por ejemplo), pero no dio nada. Soltó algo de dinero a beneficencias y cosas del estilo, pero por ejemplo Bill Gates ha dado muchísimo más, y además es algo que hacen casi todos los millonarios en el mundo.

Jobs no creó curas, no creó conocimiento, no creó arte (y no, aunque él era la cabeza de Pixar, una vez más él no era el creador ahí), no creó (para motivos prácticos) tecnología. Sólo vendió, y vendió, y vendió. Y está bien: así funciona el capitalismo, Jobs fue de los pocos afortunados que sin crear por él mismo algo nuevo se hinchó de lana de forma ridícula. Bien por él; incluso podría yo estar de acuerdo en que se lo merecía.

Pero por favor no me vengan conque hay que estarle agradecido por nada.

Se murió; eso está gacho. Particularmente porque dicen que el cáncer de páncreas (que me parece es el que tenía) es de los más dolorosos que existen. Sinceramente, qué mala onda por su familia y por sus seres queridos.

Pero el mundo no perdió más que un excelente vendedor. No es como cuando murió Gandhi, o Einstein, o ni siquiera John Lennon (que en su corta vida creó muchísimo más en arte que Steve Jobs).

Así que me parece pertinente algo de mesura. Murió un ser humano, uno muy famoso, y con un especial talento que utilizó de forma perfecta para volverse millonario. Como con cualquier vida humana terminada antes de tiempo, es una tragedia. Pero sólo anteayer murieron más de 70 personas en Mogadiscio por una bomba; hace tres días encontraron cinco cabezas en México.

Si alguien en México se lamenta más por la pérdida de Steve Jobs, que por los 40,000 muertos relacionados con el narco que han habido en el país durante el “sexenio” del animal que usurpó Los Pinos en el 2006, me parece que sinceramente deberían reconsiderar sus prioridades.

GNOME 3.2

GNOME 3.2 tuvo a bien salir este 28 de septiembre. Como estaba en Toronto y faltaban dos días para que yo volara a California, hice lo que cualquier persona sensata hubiera hecho.

Lo instalé de inmediato.

Por supuesto esto causó que el escritorio en mi laptop tuviera a bien morirse un rato, pero es el tipo de cosas que uno debe dar por sentado si planea hacer pendejadas de este estilo. Después de menearle tantito por fin lo compuse, y ahora está jalando bastante bien, aunque tuve que desactivar mis extensiones de System Monitor y Weather. Las dos son básicamente inútiles: aunque me gusta tener la información de la carga de mi sistema, y qué temperatura hace afuera, no las necesito para nada. Y seguramente las portarán a 3.2 en unos días o semanas. También tuve que desactivar mi extensión Auto Move Windows, pero esa sí me duele, porque me parece utilísimo poder decirle a mi laptop en qué escritorio quiero qué aplicaciones. Esa también la portarán pronto, espero.

Como sea, Slashdot y OSNews sacaron las historias correspondientes, que yo no había leído por tener mi máquina desconchavadita, y por andar volando a gringolandia. Hoy las leí, y me impresionó el odio que mucha gente sigue expresando contra esta nueva versión de GNOME. Eso lo entiendo; como he comentado muchas veces, siempre es así en Linux cuando nuevas tecnologías se introducen.

Lo que no entiendo es las acusasiones de que GNOME 3 baja la productividad de alguien. Ahora que estuve escribiendo artículos en Toronto, mi laptop la usé de hecho para trabajar, y no sólo para divertirme como normalmente hago. Y si algo me impresionó de GNOME 3, es cómo ayuda a trabajar. Especialmente la capacidad de casi no necesitar usar el ratón nunca para iniciar aplicaciones y cambiar entre ellas. Que las notificaciones aparezcan abajo y sin llamar mucho la atención es maravilloso, especialmente para gente como yo que se distraen porque vuela la mosca.

Casi todas las críticas que he leído son “reparables” (entre comillas, porque me parece que es una cuestión más de gusto que de mal diseño) con extensiones, y además me extraña que más gente no se percate de lo que las mismas harán por GNOME. Como comentaba hace unos días, configuré Emacs para que se hablara con Evince y pudiera saltar del editor al visor y de regreso (lo cual es endemoniadamente útil al estar escribiendo \LaTeX… pero no “configuré” Emacs realmente. Lo extendí usando Emacs Lisp.

Con un GNOME 3.2 extensible vía JavaScript va a ser lo mismo, sólo que usaremos diccionarios (hashes) en lugar de listas. Los desarrolladores de GNOME están tratando de restringir lo que las extensiones podrán o no hacer, para que no presenten un problema de seguridad para usuarios “normales”: pero para power users como yo dichas restricciones serán completamente irrelevantes, y podremos extender GNOME a que haga todo lo que queramos. Yo estoy por ejemplo pensando en una extensión para escribir entradas en mi blog (regresé a usar el navegador porque Drivel comenzó a hacer cosas que no me gustaban con el HTML de mis entradas), y a lo mejor hacer dicha extensión a su vez extendible, para lidiar con casos como el mío, que las imágenes que agrego en mi blog siguen un formato muy específico.

Es uno de miles ejemplos que me vienen a la mente: JavaScript es al fin y al cabo Turing completo, y (como quince millones de veces más importante) completamente interpretado. Lo que quiere decir que escribir, probar y actualizar extensiones será tan trivial como aventar un archivito .js en un directorio y luego modificarlo.

Esa es la idea, me parece, que están persiguiendo los desarrolladores de GNOME: el caché que va a tener ahora el escritorio vendrá de la cantidad de extensiones que tendrá disponible para hacer pendejaditas. No hablo de las aplicaciones, que esas básicamente están cubiertas: hablo de extensiones que estarán completamente integradas al escritorio, incluyendo el look and feel, y que únicamente funcionarán para GNOME.

Se ve divertido el futuro.

Y ahora de regreso también

Hace poco más de un mes, describí cómo configurar Emacs para ligarlo a Evince, de tal forma que si compilamos un archivo \LaTeX a PDF con la opción -synctex=1, y al hacer Control-click en una parte del PDF, Emacs enmarque el archivo .tex en la línea correspondiente.

A los pocos días Omar me comentó que sí servía, y se quejó amargamente de que no funcionaba al revés: que dentro de Emacs mi código (que estaba basado en en el de aquí) no permitía saltar dentro del PDF a la región de texto correspondiente al archivo .tex.

Por supuesto, una vez más, sí se puede: estamos hablando de Emacs al fin y al cabo. Sólo que yo estaba atareadísimo terminando de escribir un artículo y las notas para otro, y los fines de semana yendo a la CN Tower y a las cataratas del Niágara, y no había tenido tiempo de revisar el código. Además, es Emacs Lisp, que la verdad (como todos los lenguajes tipo Lisp) tiendo a aborrecer ligeramente.

Por fin hace unos días revisé el código, y lo primero que hice fue corregir y mejorar algunas cosas de la primera parte, lo que hace que Evince se comunique con Emacs. El código funcionaba porque el alcance de las variables en Emacs Lisp no tiene sentido; en un lenguaje más sensato hubiera fallado miserablemente. Corregí eso y así quedó:

(require 'dbus)

(defun goto-line-and-recenter (line col)
    (goto-line line)
    (recenter line)
    (raise-frame))

(defun synctex-find-file (buf line col)
  (find-file buf)
  (goto-line-and-recenter line col))

(defun synctex-switch-to-buffer (buf line col)
  (switch-to-buffer buf)
  (goto-line-and-recenter line col))

(defun evince-backwards-sync (file linecol time)
  (let ((buf (get-file-buffer (substring file 7)))
        (line (car linecol))
        (col (cdr linecol)))
    (if (null buf)
      (synctex-find-file (substring file 7) line col)
      (synctex-switch-to-buffer buf line col))))

(dbus-register-signal
 :session nil "/org/gnome/evince/Window/0"
 "org.gnome.evince.Window" "SyncSource"
 'evince-backwards-sync)

Quedó un poquito más corto y más bonito; aunque en GNOME 3 sigue sin levantar la ventana de Emacs cuando se enmarca el documento (otra queja amarga de Omar). En otros escritorios debería levantarla: no tengo acceso a otros escritorios ahorita, y aunque lo tuviera la verdad me da mucha hueva comprobarlo.

Después comencé a ver el otro lado, que Emacs se comunique con Evince, y resulta que es similarmente sencillo, exceptuando el hecho de que Emacs Lisp es de esos lenguajes idiotas que dicen ser débilmente tipificados, lo cual significa que los tipos fallan justo cuando uno no quiere que fallen. Siendo justo, el problema realmente es que DBus es fuertemente tipificado, y entonces a veces hay que darle una manita a Emacs Lisp para que sepa cuál es el tipo que debe enviar por el cable (de ahí los feos :int32 que de repente aparecen en el código).

El código correspondiente me quedó así:

(defun get-evince-document (file)
  (dbus-call-method
   :session "org.gnome.evince.Daemon" "/org/gnome/evince/Daemon"
   "org.gnome.evince.Daemon" "FindDocument"
   (concat "file://" (replace-regexp-in-string "tex$" "pdf" file)) t))

(defun evince-forwards-sync (file line col)
  (dbus-call-method 
   :session (get-evince-document file) "/org/gnome/evince/Window/0"
   "org.gnome.evince.Window" "SyncView"
   file (list :struct :int32 line :int32 col) 0))

(defun current-line-number ()
  (1+ (count-lines 1 (point))))

(defun do-evince-forwards-sync ()
  (interactive)
  (if (not (null (buffer-file-name)))
      (if (not (buffer-modified-p))
	  (if (string-equal (substring (buffer-file-name) -4)
			    ".tex")
	      (if (file-exists-p (replace-regexp-in-string 
				  "tex$" "pdf"
				  (buffer-file-name)))
		  (evince-forwards-sync (buffer-file-name)
					(current-line-number) 1)
		(message "You need to PDFLaTeX your file."))
	    (message "You can only forward sync LaTeX files."))
	(message "You need to save your buffer first"))
    (message "Forward sync only works in file buffers.")))

Además yo en particular puse

(global-set-key (kbd "< f1 >") 'do-evince-forwards-sync)

en mi .emacs, así que ahora si le pico F1 a mi compu mientras Emacs está en un archivo .tex que esté salvado, inmediatamente manda al PDF a la página correspondiente en Evince. De nuevo, GNOME 3 no permite que una aplicación le robe el foco a otra, así que Evince no se levanta, pero debería hacerlo en otros escritorios.

Está bastante padre cómo funciona el asunto, y además funciona (me parece) de forma suficientemente robusta. Ciertamente espero usarlo mucho durante los próximos artículos que escriba.

Porque no tengo cosas importantes que hacer

Leyendo los comentarios de un blog, me encontré con esto. Es un estúpido jueguito parecido a Space Invaders, con una muy importante distinción: los “enemigos” son palabras, y para “matarlos” uno tiene que escribir dichas palabras.

Ztype

Ztype

En otras palabras, es un juego para ejercitar touch typing, el teclear rápidamente sin mirar el teclado. Después de cinco niveles tuve que forzarme a cerrar el tab de mi navegador, porque me percaté de que iba a perder todo el día ahí si no lo hacía. No sé cómo nunca se me había ocurrido un juego de este estilo: es de hecho útil, porque teclear sin mirar el teclado es de las primeras cosas que uno tiene que aprender para escribir rápido (ya sea prosa, \LaTeX, o código).

Si quieren tomarse un descanso de diez minutos mientras trabajan en la oficina, el jueguito no sólo es entretenidísimo, además sirve para practicar su touch typing.

Mi Xperia Play es divertido, pero hay niveles

Platicando con Juanjo:

Yo: Ayer vi un PS3 nuevo, pero con la caja abierta, en 224 CAD. Pensé seriamente en comprarlo.
Juan: Pero el nuevo (con la caja cerrada) cuesta $250, ¿no?
Yo: Ajá. $249, de hecho. Eso es 10% de descuento.
Juan: Si es un lugar confiable, pues te ahorras esos $25, pero si no, yo preferiría comprarlo nuevo nuevo.
Yo: Pues es Best Buy.
Juan: Ah.
Yo: Como sea, 250 CAD (o USD) están de no manches.
Juan: La cosa es que ya tienes uno, ¿no?
Yo: La cosa es que está en México.

Ah claro, uso Emacs

Cuando entré a mi posgrado, con esto de escribir notas, preámbulos extendidos y artículos, fue necesario que volviera a escribir documentos en \LaTeX regularmente, algo que había dejado de hacer durante mi carrera profesional, porque fuera de la academia \LaTeX es básicamente inútil. El PDF de mi novela fue generado por \LaTeX, pero la escribí en un archivito de texto con un formato que me inventé, y luego escribí un script en Perl que generaba un documento \LaTeX que luego compilaba a PDF. Y eso sólo porque soy un geek: la gente normal no usa \LaTeX, y la verdad no tendría razones para hacerlo.

Como sea, yo volví a escribir en \LaTeX, y para mi tesis de maestría decidí pasarme a la “modernidad” y escribirla en gedit. La verdad gedit es un editor más que pasable, y pude terminar mi tesis de maestría usando su plugin para \LaTeX, pero al final sí estaba sufriendo porque yo fui indoctrinado en el mejor editor que nunca jamás el mundo ha tenido, Emacs.

Sin embargo, después de utilizar Emacs al inicio de mi licenciatura, me pasé a XEmacs por una combinación de factores ligeramente idiotas. Entre ellos estaba el hecho de que leía mi correo electrónico dentro del editor, y eso funcionaba mejor en XEmacs que en Emacs, así que obviamente me pasé al primero. La verdad no lo lamento: XEmacs fue un gran editor durante muchos años, y yo lo usé alegremente como programador profesional, incluso durante las tristes ocasiones en que tuve que programar en Visual Studio (el editor de Visual Studio apesta).

Así fue hasta que llegó el momento de escribir mi tesis de maestría, cuando me pasé a gedit porque, entre otras cosas, XEmacs pasaba unos momentos dolorosos en ese entonces, donde el grupo de desarrolladores que lo mantenían no sabían qué hacer con múltiples tecnologías modernas que reemplazaban y/o mejoraban varios hacks que el venerable editor había tenido que implementar por su cuenta. Cosas como UTF-8, fuentes con antialias, y bibliotecas de interfaces gráficas, Emacs y XEmacs las habían tenido que implementar por su cuenta (¡y en Emacs Lisp, ‘arajo!), y cuando implementaciones más sensatas hicieron su aparición, los desarrolladores de ambos editores tardaron un rato en adaptarse a las nuevas tecnologías.

Así que escribí mi tesis de maestría en gedit, pero sí estaba sufriendo al final, extrañando todas las maravillas que Emacs y XEmacs siempre han ofrecido. Ya en el doctorado regresé a XEmacs, pero pronto descubrí que Emacs ahora jalaba mucho más bonito (se integra con GTK+ y tiene mejor soporte para fuentes con antialias), y regresé a usarlo después de casi diez años de tenerlo abandonado.

La cosa es que sencillamente ningún otro editor (con la posible excepción de Vim) le llega ni siquiera al ombligo (creo que sí ya superaron sus talones). El editor de GNOME, gedit, es endemoniadamente bueno, y con sus extensiones en Python se acerca (al menos potencialmente) al nivel de funcionalidad que Emacs ofrece al estar escrito, básicamente, en Emacs Lisp; sin embargo, la capacidad de navegar un documento rápidamente utilizando solamente el teclado es algo que está inherentemente alambrado en Emacs. Los algoritmos (y la implementación de los mismos) fueron escritos originalmente hace casi treinta años, y han sido refinados y optimizados durante todo ese tiempo. Es realmente ilusiorio esperar que el triste widget de texto de GTK+ (por bueno que sea) se le pueda acercar.

Llevo ya un par de años escribiendo \LaTeX en Emacs y siendo bastante rápido, pero había una cosa de las Mac que envidiaba. En las Mac, en TeXShop, uno está trabajando en un documento \LaTeX, y tiene el PDF compilado al lado, y si uno le pica a alguna parte del PDF y le dice que haga “sync”, el editor de texto se centra “cerca” del código \LaTeX que generó la parte del PDF clickeada. La tecnología es llamada SyncTeX, y de hecho Evince (mi visor de PDFs de siempre) la soporta desde hace un rato. Sin embargo, no creí que fuera posible utilizarlo en conjunción con Emacs, porque Evince (siendo parte de GNOME) no tiene una opción para configurar qué editor debe llamarse o de qué manera esto debe ocurrir para que enmarque la parte del documento deseada. A la gente de GNOME no le gusta “confundir” a los usuarios con opciones de configuración.

En su lugar, Evince tiene una señal en DBus que se dispara cuando el PDF visto fue compilado con SyncTeX, y uno hace Ctrl-click en una parte del mismo. Esa señal la captura quién sea que se conecte a DBus, y hace lo que sea que tenga que hacerse con la información que pasa la señal (básicamente el nombre del archivo .tex, y la línea correspondiente). Entonces yo creí que no había forma de que yo pudiera hacer eso desde Emacs, porque DBus es una tecnología “moderna”.

Y por supuesto resultó que estaba equivocado porque, claro, uso Emacs.

Por supuesto algún demente implementó soporte para DBus en Emacs, y entonces fue sólo cosa de poner lo siguiente en mi .emacs:

;; SyncTex from DBus
(require 'dbus)

(defun synctex-find-file (file)
  (find-file (substring file 7))
  (goto-line (car linecol))
  (unless (= col -1)
    (move-to-column col)))

(defun synctex-switch-to-buffer (file)
  (switch-to-buffer buf)
  (goto-line (car linecol))
  (unless (= col -1)
    (move-to-column col)))

(defun my-evince-sync (file linecol time)
  (let ((buf (get-file-buffer (substring file 7)))
        (line (car linecol))
        (col (cadr linecol)))
    (if (null buf)
      (synctex-find-file file)
      (synctex-switch-to-buffer buf))))

(dbus-register-signal
 :session nil "/org/gnome/evince/Window/0"
 "org.gnome.evince.Window" "SyncSource"
 'my-evince-sync)

El código original lo saqué de aquí, y lo modifiqué para que funcionara con Evince 3 (porque yo uso GNOME 3), y para que si no estaba abierto el buffer del archivo Emacs lo abra. Con eso, y teniendo la ventana de Evince “Always on top”, funciona casi exactamente igual que en un Mac:

Emacs con SyncTeX

Emacs con SyncTeX

De hecho funciona mejor; hasta ahora, el editor se centra exactmente en la línea correcta, no como en TeXShop.

Esto me hizo reevaluar mi opinión acerca de que Evince no tenga una opción para especificar qué editor debe “actualizarse” cuando uno hace Ctrl-click: la idea de que el visor sólo dispare una señal, y que sólo los editores conectados a DBus respondan como debe de ser me parece fabulosamente elegante, y ciertamente mucho más flexible y poderosa. Tal vez la gente de GNOME de hecho tenga la razón con esta idea de no ofrecer miles opciones de configuración a lo idiota (el plugin de gedit que aprovecha SyncTeX funciona igual: sólo se conecta al bus y captura la señal correspondiente de Evince).

Fue divertido que mi fe flaqueara un poco, ¿cómo pude dudar que hubiera algo que Emacs no puede hacer? Si como todo mundo sabe, Emacs es un buen Sistema Operativo; lástima que le falte un buen editor.

Real Programmers

Real Programmers

Como en 1998

Hace unos cuantos días salió GNOME 3.0. Como mis lectores habituales ya sabrán, vengo usando GNOME básicamente desde su versión 1.0… y de hecho comencé a usarlo desde antes, probando betas y cosas por el estilo.

Así que, a pesar de que llevo ya algunos años tratando de mantener mis sistemas Gentoo en forma “estable”, decidí que intentaría esta nueva encarnación de GNOME, que promete ser un cambio radical a todo lo que estamos acostumbrados con escritorios de computadora.

Después de compilar durante día y medio los nuevos paquetes, traté de utilizarlo y como Murphy debió prever, no funcionó. Se moría a menos de un minuto de comenzar a usarlo. Recompilé todo de nuevo: seguía fallando. Le moví a todo lo que yo podía suponer que podía moverle, levanté el nuevo shell de GNOME en gdb, hice magia blanca, gris, negra, y lo que sigue de negra.

Y nada.

Yo esperaba algo espectacularmente desastroso: bibliotecas incompatibles, errores de programación, problemas con cómo Gentoo suele hacer las cosas. Pero al final resultó que todo era culpa de un archivito XML de poco más de 150 líneas. Y lo peor es que ni siquiera era culpa del archivito en sí: sólo tenía mal el nombre.

Ya reporté el bug, y por fin puedo usar la nueva interfaz de GNOME, que ciertamente promete ser un cambio radical a todo lo que estamos acostumbrados con escritorios de computadora. Lo voy a seguir usando, porque así soy soy, pero no sé cuánto le cueste a muchas personas acostumbrarse a este cambio (muchísima gente es sorprendentemente resistente a casi cualquier tipo de cambio, no digamos un cambio verdaderamente radical). Sí me gusta, pero sí no tiene nada que ver con GNOME 2… ni con cualquier otro escritorio que yo haya usado, por cierto.

Pero estos tres días que estuve peleándome con tratar de hacer jalar GNOME 3, recordé mucho cómo eran las cosas allá por 1998. Cuando uno booteaba linux a una terminal, y a veces (no siempre) se metía a X para un par de cosas.

Hemos recorrido un largo camino.

Chiquito y barato

Después de pasarme dos semanas en maratón terminando de escribir dos artículos, mi novia y yo decidimos pasarnos el puente en el Estado de México.

Yo traje mi laptop y mi monitor LCD de 22″ porque tengo en el disco duro un par de series que estamos viendo, pero caímos en cuenta que se nos habían olvidado un cable VGA, y bocinas algo más decentes que las de la laptop, que hacen que todos los actores suenen como Alvin y las Ardillas.

Ya en el Estado de México pasamos por un Steren y ahí compré un cable VGA, pero cuando pedí bocinas me salieron conque los niños de hoy ya sólo compran bocinas USB. Se me hizo ridículo, pero cuando vi que el precio era de hecho menor que la última vez que compré bocinas (obviamente no USB), decidí que bueno, que ya qué. Ni siquiera me pasó por la cabeza que no fueran a funcionar; cada vez estoy más acostumbrado a que en Linux las cosas sencillamente funcionan.

Por supuesto, cuando en la noche antes de dormir tratamos de ver nuestra serie, las malditas bocinas no funcionaron. Era tarde, y en el cuarto no llega la señal inalámbrica, así que lo dejé así y vimos nuestra serie con las voces de Alvin y las Ardillas.

Hoy en la mañana estuve tratando de echarlas a andar, y no estaba avanzando nada hasta que una idea loca entró en mi cabeza. ¿Qué tal que lo que pasaba era que las famosas bocinas no son sólo bocinas, sino una tarjeta de sonido USB? Mi laptop sólo tiene compilado el controlador de la tarjeta de sonido de la laptop, porque quién carajos necesita soporte para más tarjetas de sonido que la que está instalada en la computadora, y eso explicaría porqué no había funcionado. Medio incrédulo compilé en mi kernel el soporte para tarjetas de sonido USB, y maravilla de maravilla, funcionó de inmediato. De hecho con la magia de PulseAudio puedo estar oyendo algo con la tarjeta de sonido de la laptop, y con un poderoso click pasarlo a la tarjeta de sonido de las bocinas USB. Funcionan bastante chido, para ser unas bocinitas chafas Steren.

Resulta que las tarjetas de sonido se han hecho tan chiquitas y baratas, que ahora es más fácil vender unas bocinas con dicha tarjeta incluida, y dejarle el proceso de sonido pesado al CPU. También se ahorran eliminador de corriente, porque el poder de las bocinas viene del mismo puerto USB.

Lo único que no entiendo es qué les costaba agragarle una entrada TRS para poder usar las bocinas de forma tonta. A lo mejor aumentaba en dos pesos el precio. Lo que importa es que hoy en la noche veremos nuestra serie sin las voces de Alvin y las Ardillas.

Sony Epic Fail

Mientras estaba con las celebraciones de año nuevo y escribiendo un artículo, parece ser que por fin crackearon para bien y para siempre el PlayStation 3.

Básicamente Sony no implementó bien el ECDSA, y en lugar de generar aleatoriamente una variable que debería ser distinta cada vez que se ejecuta el algoritmo, utilizaron un valor constante siempre. Usando esa variable el equipo llamado fail0verflow pudo reconstruir la llave privada maestra el PS3.

Dado que todos los juegos y aplicaciones que han salido hasta ahora para el PS3 han sido firmados digitalmente con dicha llave, Sony no puede sencillamente cambiarla para corregir el fallo. Algunos especulan que Sony podría intentar usar una nueva llave para juegos futuros, y hacer una “lista blanca” de qué juegos y aplicaciones son válidos para PS3 hasta ahora. Suena difícil que ocurra sin embargo; no es costeable. Además, habiendo obtenido la llave maestra los de fail0verflow pudieron echarle una mirada a todos los secretos del PS3, y lo que vieron no los dejó muy impresionados (ellos fueron los que dijeron que la seguridad de Sony es un “epic FAIL”). Como ya vieron todo el stack de seguridad del PS3 y concluyeron que no es muy bueno, incluso con una (improbable) nueva llave maestra hay ya suficiente información para crackear la consola por otros medios.

Hasta ahora lo único que ha resultado de esto es que se pueden instalar aplicaciones caseras (“homebrew”) en el PS3, lo que en particular permite instalar Linux en la consola. Los hackers que han estado trabajando en esto han sido muy enfáticos en que no les interesa la piratería; pero el hack sin duda lo permite, y no dudo que en unos días (tal vez incluso horas) comiencen a salir los firmwares que permitan respaldar juegos en el disco duro del PS3 (o uno externo conectado por USB), y jugarlos de ahí.

A mí en particular no me interesa la piratería; no juego los juegos que compré con mi dinero, dudo que jugara los que bajara de la red. Además de que qué hueva bajar gigas y gigas por un juego. Y de hecho creo que sí me gusta tener mi (pequeña) colección de juegos y mi (grande) colección de películas Blu-ray. Pero les voy a decir qué sí me interesa:

  1. Un emulador de Nintendo, Super Nintendo y SEGA Génesis, usando los controles nativos del PS3 obviamente.
  2. Software de media center que soporte todos los codecs de video y audio habidos y por haber en el universo, y que pueda leer todos los archivos de subtítulos existentes, incrustados o no en el archivo de video.
  3. Un servidor SSH, o al menos FTP; no hay nada más desesperante para mí que pasar videos al PS3 por USB.
  4. Soporte para Ext3, o al menos NTFS en discos USB; es idiota la restricción a archivos de 4GB.
  5. Todos los juegos que la comunidad Open Source ha hecho en su existencia; podemos discutir qué tan práctico es jugar Sudoku en mi PS3, pero no me importa, yo quiero hacerlo. Y no me importa si ya hay un Sudoku en la PlayStation Network, incluso si es gratis; quiero poder modificarlo, mejorarlo (o empeorarlo) yo.
  6. Poder quitarle la restricción de zona al reproductor de DVD del PS3.

Creo que eso es todo. En cuando salga alguna de esas cosas, le pondré un firmware personalizado a mi PS3; por ahora sólo no lo voy a actualizar, sólo por si las moscas.

Systemd

Estoy programando un pequeño proyecto personal desde hace un par de meses. Ahorita estoy atorado en una parte bastante mensa, así que decidí escribir acerca de un proyecto que, muy indirectamente, me inspiró a escribir el mío.

Hace ocho meses, Lennart Pottering escribió en su blog acerca de systemd, un reemplazo para el venerable (y anticuado) sysvinit (de “System V Init”). La idea en resumidas cuentas, es copiarle más o menos lo que hace Apple para iniciar rápidamente Mac OS X, y haciéndolo tomando todas las posibles ventajas que ofrece Linux. Esto último es importante; systemd por diseño no es portátil: está pensado para Linux y únicamente para Linux.

Lo principal para que funcione únicamente en Linux es el uso de Control Groups, una característica del kernel de Linux que ha estado disponible desde la versión 2.6.35, y que no mucha gente le ha hecho mucho caso, hasta hace unas semanas que Slashdot reportó una historia acerca de como un parche de 200 míseras líneas en el kernel volvía el escritorio súper usable, incluso bajo intenso uso (el ejemplo clásico siendo el compilar un kernel usando todos los procesadores). Como Lennart mismo mencionó poco después, el parche ni siquiera es necesario configurando inteligentemente los famosos Control Groups, o cgroups para abreviar.

Los cgroups son eso, grupos de control para procesos. Permiten agrupar ciertos procesos de tal forma que se les pueda asignar recursos a todos los procesos en el grupo. El “milagroso” parche lo que hacía era poner al proceso compilando el kernel en un mismo grupo, y el resto del escritorio en otro; por lo tanto el CFQ (Completely Fair Queue, la implementación del scheduler usada por omisión en Linux) les da igual tiempo de procesador al grupo compilando el kernel (aunque al llamar make le pidamos que use todos los procesadores), y a la ejecución del escritorio.

Systemd utiliza a lo bestia los cgroups; siendo un reemplazo de sysvinit, lo que hace es ser el primer proceso que es llamado por el kernel, y se encarga de todos los otros procesos que levanta después el sistema. Entonces si uno de esos procesos es Apache, por ejemplo, systemd pone a todos los procesos derivados de Apache en un grupo de control, incluyendo scripts CGI que hagan un “doble fork” para tratar de escapar del control del mismo Apache. Los cgroups impiden que tal escape sea posible.

Por supuesto, no es lo único que hace systemd distinto a sysvinit; también manda al carajo la idea de tener scripts para levantar y tumbar servicios, y los reemplaza con archivos (llamados unidades en la terminología de systemd) que son sospechosamente similares a los archivos .desktop que pululan los distintos escritorios en Linux (aunque mantiene compatibilidad para poder usar los scripts de sysvinit). El no mandar llamar a bash (y a un montón de programas a través de bash) acelera el tiempo que tarda en levantar el sistema enormemente.

Encima de esto, systemd intenta levantar cuanto proceso pueda de forma paralela, y reemplaza muchos scripts escritos en bash por sencillos programitas escritos en C. Todo esto además pensando en que systemd funcione de forma idéntica entre distintas distribuciones, lo cual tendría la alegre consecuencia de que las diferencias idiotas que hay entre todas las distribuciones para levantar el sistema desaparecerían.

Pocos meses después de que Lennart anunció systemd, traté de instalarlo en Gentoo, y fallé miserablemente. Lo cual no es de extrañar; Gentoo utiliza su propio sistema de inicio disinto de sysvinit (OpenRC le dicen), y esto ocasiona que si uno quiere intentar usar systemd, debe poder reemplazar toda la inicialización, no se puede utilizar nada de OpenRC. Lo dejé estar un par de meses más, y hace unas semanas lo volví a intentar.

Aunque no funcionó a la primera, poco a poco (y reportando bugs y parchando distintos programas y ebuilds de Gentoo), por fin logré que mi laptop utilizara systemd exclusivamente. Mi laptop pasó de tardar 1 minuto 40 segundos en inicializar, a tardar “únicamente” 50 segundos. Sé que no suena mucho, pero no es tan rápida.

Cuando por fin me sentí lo suficientemente cómodo con systemd en mi laptop, lo puse en mi media center. Ahí la diferencia sí fue significativa; mi media center inicializaba en 50 segundos, y con systemd tarda alrededor de 20 ahora.

Además del booteo indudablemente más rápido, el diseño de systemd me parece extremadamente elegante y bien pensado. Y el uso de cgroups además ocasiona un montón de cosas buenas, como el poder cambiarle la prioridad a todo un conjunto de procesos de forma sencilla. Si además consigue convertirse en el estándar para inicializar Linux a través de distintas distribuciones (Fedora y OpenSUSE, y a lo mejor Debian sí parecen serios al respecto), ciertamente esto mejoraría la vida de todos.

En mi laptop ya llevo un par de semanas usando exclusivamente systemd, y en mi media center unos días. Todavía no lo pongo en mi máquina de escritorio, pero probablemente lo haga estas vacaciones. Está muy simpático el proyectito.

World domination

En los noventas, Linus Torvalds solía hablar de “dominación mundial”, de los pasos necesarios para que Linux dominara las computadoras de todo el mundo. Era, por supuesto, en gran parte una broma; después de casi veinte años de que Linux comenzó a escribirse, el sistema operativo libre no tiene ni siquiera el 10% del mercado de computadoras de escritorio. En otros rubros le ha ido mucho mejor; en servidores casi todo mundo usa Linux; y con la adquisición de Sun por parte de Oracle, y la subsecuente muerte (para motivos prácticos) de Solaris y OpenSolaris, al parecer esto sólo se acrecentará en un futuro no muy lejano.

Otra parte donde le está yendo muy bien a Linux es en dispositivos portátiles; dícese teléfonos celulares, porque para como yo veo las cosas, todos los dispositivos portátiles van a terminar colapsándose dentro de los famosos “smart phones”. La familia de teléfonos Android está desbaratando la dominación del iPhone, que ciertamente revolucionó cómo eran los “smart phones” (que si me lo permiten, dado que no me gusta el término “smart phone”, de ahora en adelante les diré “teléfonos mamones”). Android es una versión de Linux desarrollada por Google.

Y de hecho Linux está en un montón más de dispositivos electrónicos; muchas veces sin que nos demos cuenta. Cuando compré mi telesota LCD, entre la documentación que traía venía una copia de la licencia GPL. Yo pensé que utilizaría alguna biblioteca GPL para, por ejemplo, desplegar las imágenes que uno le puede poner a través de un disquito USB, pero resulta que me equivocaba,

Mi hermano me pidió ayuda porque quería ver una película que tenía en su computadora; como es una laptop no muy nueva, le dije que sencillamente comprara un cable VGA. Después me llamó diciendo que no servía; yo por supuesto, supuse que mi tecnológicamente semi iletrado hermano habría hecho una pendejada, así que fui con mi laptop y mi propio cable VGA.

Con sorpresa vi en su casa que no servía, y entonces supuse lo siguiente; que algo había mal con su tele. Así que volví a mi casa, y con mayor sorpresa descubrí que tampoco servía en mi casa. Y entonces por fin me metí a la red a investigar un poco. Resulta que las laptops Sony VAIO suelen tener problemas para conectarse con los LCD Samsung. Nadie me ha podido explicar bien a bien cuál es el problema; lo que yo sé es que ocurre a nivel de hardware, porque falló con el Windows XP de mi hermano, y mi sistema Linux.

Buscando posibles soluciones, vi que se podía actualizar el firmware de mi tele, así que me metí a la página de Samsung y bajé el nuevo firmware. Cuál sería mi sorpresa al descubrir que dicho firmware se instala utilizando un vil script de shell. Y así fue como descubrí que de hecho mi televisión corre Linux.

Todavía no puedo conectarle una laptop VAIO, pero al menos sé que por dentro tiene software libre.