domingo, agosto 12, 2007

El próximo autobús

En economía se suele hablar de que no existe tal cosa como un almuerzo gratis, sin embargo, en informática tenemos la empírica Ley de Moore que desde 1965 nos ha estado dando continuamente nuestro almuerzo gratis.
El constante aumento de la densidad de la electrónica logra que los procesadores sean cada vez más capaces, tengan más instrucciones especializadas y más memoria de caché integrada, mientras cada vez la memoria es más densa y barata. Por otra parte, y aunque no esta directamente relacionada con la Ley de Moore, las velocidades del reloj han seguido muy de cerca el progreso de la densidad de la electrónica, creciendo exponencialmente. Este comportamiento histórico muestra que las máquinas duplican todas sus capacidades aproximadamente cada 18 meses.
En un contraejemplo económico de almuerzo gratis los diseñadores de software evaden el tema de la eficiencia confiando en la Ley de Moore, logrando que el ineficiente diseño de hoy, será la adecuada aplicación de mañana. Un ejemplo de ello son los procesadores de palabras como Microsoft Word u OpenOffice Writer, que se sienten como un viejo Wordstar en un procesador Z80 de 8 bits a 2Mhz, con 64KB de RAM, a pesar de que ahora las máquinas tienen procesadores de 64 bits a más de 2000MHz, con 32 mil veces más RAM.
Sin embargo, hace algún tiempo que las velocidades de reloj de los procesadores no aumentan, este problema no apareció ayer, ni hace un semestre, el progreso en la velocidad de los procesadores comenzó a estancarse a finales del 2002!, como se puede apreciar en la figura. Para esa época ya existían procesadores de más 3GHz, por lo que hoy deberíamos tener procesadores de más de 14GHz, pero no es así.



Las razones de este estancamiento tecnológico son de carácter físico, y tienen que ver con la velocidad de la luz y la disipación de energía. Parece poco probable que logremos nuevas tecnologías que permitan superar las barreras encontradas. Ciertamente los fabricantes tienen en sus laboratorios procesadores de mayor velocidad, pero estos deben ser refrigerados con equipos poco prácticos para su implantación.
Debido a estos problemas, los fabricantes de procesadores se han visto forzados a aumentar las capacidades de sus productos por la vía del paralelismo. En el 2003 comenzaron los anuncios sobre múltiples unidades de procesamiento (cores) en un solo procesador, hoy los procesadores "dual core" son populares y los procesadores "quad core" están comenzando a comercializarse. Intel por su parte predice que fabricará procesadores con más de 100 "cores", y según la ley de Moore, deberían aparecer en unos 8 años suponiendo que no aparezcan nuevos problemas (se me ocurren problemas de eficiencia en el mantenimiento de la coherencia del cache).
La consecuencia inmediata de esta forma de crecimiento es: que a menos que el software sea diseñado para operar concurrentemente, no se obtendrá ningún beneficio de los nuevos procesadores. En otras palabras: se acabó el almuerzo gratis.
Algunas personas argumentarán que los sistemas operativos de hoy en día, como Linux están preparados para esto, y podremos ejecutar más procesos o hilos (threads) en nuestras máquinas, sin embargo, esta visión simplista no es completamente cierta ni siquiera en el caso de los servidores, pero está fundamentalmente errada en el caso del escritorio, donde generalmente utilizamos una aplicación al mismo tiempo.
Así como la década de los 70 fue la revolución de la programación estructurada, y la década de los 90 fue la revolución de la programación orientada a objetos, la próxima década será la revolución de la programación concurrente.
No voy a ahondar en el tema de los costos y beneficios de la programación concurrente, porque el artículo de Herb Sutter, del cual tomé la introducción, lo explica con gran detalle. Sin embargo, quiero resaltar algunos de los problemas que menciona su autor, y otros, que me ha tocado sufrir en carne propia.
  1. A los programadores les cuesta mucho más razonar sobre programas concurrentes, que sobre programas que tienen un solo flujo.
  2. Las aplicaciones no siempre son fácilmente paralelizables.
  3. Las principales técnicas que utilizamos actualmente para manejar concurrencia implican el uso de memoria compartida, cierres y semáforos, esto nos lleva irremediablemente a un universo de regiones críticas, carreras y abrazos mortales.
  4. Aún los threads nativos del sistema operativo consumen demasiados recursos y no escalan muy bien cuando la concurrencia aumenta notablemente.
Los programadores deben aprender a plantear y razonar las aplicaciones y sistemas como colecciones de hilos o procesos que llevan a cabo las funciones que los componen, de la misma manera en que antes aprendieron a utilizar los objetos. Sin embargo no creo que deban aprender los detalles acerca de los mecanismos que mencioné en el problema 3.
Evitar estos problemas requiere de programadores con conocimientos técnicos avanzados, alta capacidad de atención y mucha experiencia en el área. Mientras detectar y eliminar las carreras y escenarios de abrazos mortales en sistemas en producción es más un ejemplo de lucha contra las artes obscuras que una ciencia. Por ello es fundamental eliminar el manejo de la memoria compartida como se eliminó el manejo explícito de la memoria en los lenguajes modernos.
Para todos aquellos que crean que esto no se puede lograr, piensen en la última vez que utilizaron alguno de estos mecanismos para compartir datos o serializar el acceso y la actualización de un grupo de registros en SQL. Así que lograr abstracciones que permitan el acceso y actualización concurrente en ausencia de mecanismos de bajo nivel, es equivalente a la eliminación del goto y las variables globales, en favor de las estructuras de control y los objetos.
Algunos lenguajes ya han introducido algunos mecanismos para controlar la concurrencia; las construcciones al estilo de syncronize en Java son un ejemplo (de muy bajo nivel), lo mismo sucede con bibliotecas como ACE y Boost en C++, que ofrecen muchos mecanismos para implementar concurrencia, todos ellos de bajo nivel.
Se estarán preguntando: ¿qué es lo quiere este señor, una papita pelada?, y la respuesta es sí; pienso que se necesitan abstracciones mucho mejores, ejemplos de ellas son: la memoria transaccional, el modelo de actores, las promesas o futuros, entre otros. Todas ellas eliminan la necesidad de aprender los mecanismos de bajo nivel expresados en el problema 3.
Algunos lenguajes como E, Oz/Mozart, Io, etc. implementan el modelo de actores, sin embargo no atacan los problemas 2 y 4.
Existen lenguajes donde la concurrencia es una parte natural del planteamiento del problema, por ejemplo, en Erlang se utilizan procesos para limitar la propagación de los errores, y como efecto secundario los programas se conciben como grupos de procesos cooperativos. Este lenguaje resuelve 1, 3 y 4, y ayuda con 2. En un programa que estoy haciendo tengo unos 500 procesos, de los cuales siempre hay como 100 trabajando, así que el programa puede beneficiarse de hasta 100 procesadores. Para aquellos que piensen que 500 procesos son demasiados, recuerden que mencioné que este lenguaje resuelve el problema 4, aunque el cómo lo logra es otra historia.
Finalmente los lenguajes funcionales completamente puros como Haskell o el descrito en un artículo anterior podrían ofrecer la máxima solución. En estos lenguajes el programador no sabe el orden de evaluación, es el compilador el que elige el orden de ejecución, pudiendo paralelizar lo que considere necesario.
La investigación en torno a la concurrencia en Haskell tiene apoyo financiero y es diversa, experimentando en varias direcciones, algunas de las cuales podrían eliminar completamente todos los problemas de la lista.
La lista no termina aquí, existe toda una gama de lenguajes desde JoCaml, que utiliza sistemas formales (léase álgebras) para expresar la concurrencia a muy alto nivel hasta Cilk y Java que ofrecen capacidades de expresión de concurrencia en bajo nivel.
Sólo resta saber si las preferencias de los programadores se inclinarán a aprender los detalles de la concurrencia, o aprender nuevos lenguajes que brinden mejores niveles de abstracción del paralelismo. En mi experiencia los primeros son tan difíciles de perfeccionar que obligarán un cambio fundamental en los requerimientos del programador, así, que los lenguajes que no ofrezcan características integradas de alto nivel para el manejo de la concurrencia, no abordarán el autobús del paralelismo, y en consecuencia serán arrollados por éste.
Por supuesto que siempre está el camión de recursos que se pueden gastar en un lenguaje como Java, para agregarle caramelitos que logren mitigar un poco la falta de mecanismos serios de manejo de concurrencia, mientras los programadores ignoran el universo de posibilidades existentes en el exterior de la matriz. Con mucho menos del 10% de lo que se gastaría en esta nueva aventura, podríamos tener un lenguaje concurrente capaz de resolvernos todos los problemas mecanizables de bajo nivel, mientras los programadores se ocupan de describir procesos y algoritmos mucho más difíciles de mecanizar.

3 comentarios:

Felipe dijo...

Oye, este blog está muy interesante. Por favor no te vayas a quemar, porque veo que andas escribiendo muchos posts rápido y como no veo muchos comentarios tal vez te desanimes. ;)

Me confienso "programador" en Java y aunque entiendo muy bien tus criterios para elegir un lenguaje para el futuro, creo que todavía me quedo con él por un rato. Ahora, si tuvieras que elegir un lenguaje que maneje este paradigma del paralelismo y que a la vez sea práctico hoy en día (es decir, haya una comunidad numerosa y en crecimiento y con bibliotecas y código disponibles para diferentes SO), cuál sería?

PD:
Por cierto, te conocí cuando hubo el Foro de Conocimiento Libre en Maracaibo.

Jose Rey dijo...

Puedes intentar Scala que comenté en: http://radarlibre.blogspot.com/2007/08/el-lenguaje-de-programacin-scala.html, tienes todo Java detrás pero un lenguaje mucho mejor que Java.

Leninmhs dijo...

Los argumentos para defender y apoyar la programación concurrente, consideras sean validos cuando la computación cuántica sea un hecho tangible. Con la computación clásica tal como la conocemos hoy en día es perfecto apuntar al futuro con la concurrencia para el aprovechamiento tanto del hardware como bien explicaste, como el software.

En mi opinión el futuro apunta a la computacion cuántica, en esta la programación funcional (haskel o similares) fuerte campo de investigación en cuanto a la implementación de algoritmos cuánticos. aquí: http://www.dcs.gla.ac.uk/%7Esimon/publications/QPLsurvey.pdf

interesante articulo respecto a lenguajes de programación cuánticos'

Excelentes artículos'
Saludos y éxitos'



--
Lenin Hernández
2.6.18 on Debian Lenny
CUCLUG.: "no hay nada mejor, que hacer lo que realmente te gusta".
Linus Tordvalds.: "If it compiles, it is good, if it boots up it is perfect".