Saber cómo funciona las cosas es más importante de lo que parece. Al menos, tener una idea general.

Nosotros, los informáticos, necesitamos saber cómo funcionan las herramientas que utilizamos. En ocasiones, ésta es una tarea dura. En otras, muy gratificante.

La semana pasada traduje un artículo, El compilador de Python desde dentro, donde se analizaba a fondo este compilador.

Hay muchas otras cosas que saber. Algunas son genéricas; otras más específicas. Pero sin duda alguna, todas importantes.

Este artículo es una mezcla de algunas de ellas.

¿Te has planteado alguna vez…?

  • ¿Por qué siempre recorremos las tablas por filas y no por columnas? ¿Habría alguna diferencia?
  • ¿Debería tratar de optimizar mi código? En caso afirmativo, ¿cómo lo hago?
  • ¿Debería implementarme mi propio algoritmo quicksort para ordenar vectores más rápido?
  • Me han dicho que .Net es la caña, ¿programaré más rápido?
  • Necesito un canal de comunicaciones, ¿qué puedo usar?

Éstas y muchas otras son preguntas habituales en nuestra profesión. Y son mucho más importantes de lo que parecen. La diferencia entre conocer las respuestas y no saber qué responder puede ser la diferencia entre ganar unas décimas de segundos o el éxito y el fracaso.

Vamos a jugar. A continuación iré poniendo las respuestas a esas preguntas, pero me gustaría que las respondierais vosotros mismos. Apuntadlas.

En las secciones siguientes os daré mi contestación. Veréis la respuesta rápida y la larga. Quizá no estéis de acuerdo… ¡Estupendo! Así podremos discutirlo. Si se os ocurre vuestra propia pregunta, también podemos discutirla :D

Recorriendo tablas: ¿filas o columnas?

Respuesta rápida: por filas es mucho más rápido.

Aunque la pregunta parece fácil, realmente es una de las más complejas de las que he planteado. Para saber la respuesta, será necesario saber un poquitín de arquitectura de computadores.

La lectura de disco es lenta. Así que los sistemas operativos tratan de optimizar esta lectura mediante cachés de bloques con tamaño fijo. Por esa razón, aunque quieras leer un byte, el sistema leerá un bloque completo (el tamaño dependerá de muchas cosas). Así, si lees el siguiente, ya se encuentra en la caché y no tiene que ir a disco, resultando mucho más rápido. Cuando un bloque lleva bastante tiempo sin usarse, se elimina de esa caché.

Si leemos un vector por filas, muy probablemente el siguiente elemento ya se encuentre en la caché. Si lo leemos por columnas, es mucho menos probable. En este segundo caso, además, si la tabla es muy grande, es posible que cuando cambiemos a la siguiente columna la primera fila ya no se encuentre cacheada y volvamos a tener un fallo de página, resultando desastroso para el rendimiento de nuestro algoritmo.

¿Es así siempre? No.

Puede ocurrir que nuestros objetos sean tan grandes que ocupen justamente un bloque. En este caso nos daría igual usar filas o columnas. Pero mejor no contéis con ello :D

También puede ocurrir que nuestro objetos sean punteros a otros objetos. En este caso también sería más rápido por filas, ya que los punteros serán accesibles más rápido y, con bastante probabilidad, apunten a lugares cercanos (que también quedarían cacheados).

¿Optimizo el código?

Respuesta rápida: ¿Qué es optimizar?

No soy gallego, aunque responda con una pregunta; realmente depende de qué queramos optimizar. Para empezar, se me ocurren 3 opciones:

  • Optimización en velocidad, haciendo que nuestro programa sea más rápido.
  • Optimización en espacio, para que ocupe menos.
  • Optimización en legibilidad, de manera que sea más mantenible.

Mejorar la velocidad es complejo, salvo que se sepa cómo funciona el compilador. Se pueden echar manos: en algunos lenguajes interpretados como PHP, es mucho más rápido una hash de propiedad-valor que crear un objeto para tal evento. En el caso de la hash, la búsqueda se realizará en C. Si usamos un objeto, las propiedades y funciones se organizan como una hash…

Mejorar el espacio es aún más complejo. En general, el código es lo que menos ocupará de toda la aplicación, así que… ¿Es realmente necesario?

Por último, la legibilidad. Es lo que se suele conocer también como refactorización. Es arriesgada: ¿por qué modificar algo que ya funciona? Pueden introducirse nuevos errores. Por eso se recomienda utilizar TDD :D

Cuando mejoramos la legibilidad hacemos que el programa sea más fácil de gestionar y que realizar un cambio resulte más sencillo. Hay muchos detractores de este tipo de acciones, pero no he conocido a nadie que no haya tenido que hacer una refactorización en algún momento.

Hay ocasiones en las que una de estas optimizaciones entra en conflicto con otra. Por ejemplo, en los lenguajes interpretados (PHP, Ruby, Python, Java, …), un objeto ocupa bastante memoria (en PHP unos 30 bytes como poco; en Python unos 50), y es posible que no ofrezcan más de lo que hace una simple Hash. ¿Por qué no utilizar una hash entonces? Porque dificulta la legibilidad, aunque es posible que sea más rápido.

Hay otra cosa a tener en cuenta: las optimizaciones del compilador. Si el compilador va a hacerlo por nosotros, ¿por qué molestarse? Por eso en C y C++ no resulta interesante realizar optimizaciones de espacio o velocidad: el compilador, seguramente, lo hará mejor que nosotros. En lenguajes interpretados (Java, Python, Ruby, PHP,…) esta optimización no existe, así que más vale que dediquemos un poco de tiempo a pensar en ella :D

¿Reimplementar QuickSort?

Respuesta rápida: No.

Por muy listo que seas, probablemente el algoritmo de tu lenguaje sea más rápido, esté más optimizado, más revisado o, simplemente, no puedes hacer algo más rápido.

Si estás en C o C++, los algoritmos de ordenación llevan probándose más de 20 años. Con optimizaciones constantes.

Por el contrario, si estás en un lenguaje interpretado, simplemente no puedes hacer algo mejor. Tu implementación será interpretada y, por mucho que lo intentes, más lenta que la existente en el lenguaje nativo, ya que es probable que esté escrita en C.

¿Cambiar de lenguaje me hará más rápido?

Respuesta rápida: No.

Si ya sabes manejar un lenguaje, ¿te hará más rápido otro? No, ya que necesitas mucho tiempo para aprender a usarlo. No me refiero a las estructuras básicas; éstas son fáciles. Me refiero a conocer el compilador, saber qué optimizaciones son fáciles y debes usar, saber elegir una función sobre otra parecida porque es más rápida, … Este tipo de aprendizaje.

Sin embargo, cambiar de lenguaje puede darte perspectiva, conocer nuevos paradigmas puede ser interesante cuando vas a tratar de atacar un nuevo problema.

Además, cada lenguaje tiene distintas construcciones y distintas maneras de implementar sus patrones. Algunos tienen sus propios patrones. Este tipo de actividad siempre es positiva.

Pero tu velocidad no aumentará de la noche a la mañana. Quizá sí que lo haga a la larga, pero no sólo por el hecho de cambiar.

¿Qué middleware usar?

Respuesta rápida: Usa Rest con HTTP.

Vale, sí: no suele ser la mejor opción, pero siempre digo que es una opción que debe plantearse.

La verdad es que depende. Depende de qué estés haciendo, del tamaño de tus mensajes, de para qué lo quieres.

Si necesitas velocidad de comunicaciones, con mensajes cortos, con respuesta inmediata, entonces deberías usar un middleware binario, como CORBA, ICE o Apache Thrift.

En caso de que busques algo para procesar mensajes grandes, en la que el tiempo de respuesta no es importante o bien no tienes suficientes recursos para responder en el momento, entonces estás buscando una cola de mensajes como RabbitMQ, Apache Active MQ o ZeroMQ.

Si quieres algo genérico, que te permita gran flexibilidad y modificar el protocolo de comunicaciones en el futuro, o necesitas que funcione en cualquier parte con pocos recursos… Entonces usa HTTP y cúrrate un protocolo Rest.

Despedida

Bueno, estas son algunas preguntas que creo que todos nos hemos hecho alguna vez. Espero que os hayan gustado y que, si se os ocurre alguna otra, ¡no dejéis de hacerla! (aquí o a vosotros mismos)