En esta ocasión compartiré algo sobre
Haskell, un maravilloso lenguaje de programación que sigue el
paradigma funcional y que venimos empleando en algunos proyectos de la empresa (
ICTEC SAC), tratando de elevar el rendimiento de nuestras aplicaciones pero sin perder esa simplicidad que
Python nos ofrece; es así que daré una pequeñísima introducción al lenguaje tomando como "hilo" sus características de paralelismo, es decir el cálculo simultáneo de operaciones en diferentes núcleos de uno o varios procesadores (ideal para clusters o computación de alto rendimiento).
Programar en
Haskell implica adentrarse en la programación declarativa y basada en funciones matemáticas, a diferencia de la programación imperativa (donde se encuentran la mayoría de lenguajes de programación) que es más bien procedimental y algorítmica. De programación funcional hablaré en un artículo más adelante porque hoy, me quiero concentrar en el potencial de
Haskell para los desarrolladores y particularmente en emplear los dos núcleos de mi Core2Duo a demanda. Recuerden que empleo
Ubuntu Jaunty, por lo que los procedimientos de instalación pueden ser diferentes en otras distros, pero el código es aplicable en cualquier SO.
Por qué programar en
Haskell? Hay muchas razones, pero una de las mejores es el rendimiento, para ello revisen este enlace http://shootout.alioth.debian.org/u64/which-programming-languages-are-fastest.php ,sin obviar el código corto, limpio, con mayor mantenibilidad, etc.
Lo usual es que Haskell no venga integrado en nuestro SO, pero gracias a los repositorios que ofrece Ubuntu instalarlos es muy simple
sudo apt-get install ghc6 libghc6-parallel-prof
Hecho esto, debemos saber que Haskell nos ofrece un intérprete interactivo y claro, un compilador. Para acceder al intérprete interactivo lo hacemos con:
ghci
Listo! Ya lo tenemos instalados, ahora a emplearlo y en esta ocasión he escogido la clásica secuencia fibonacci para demostrar el rendimiento de este lenguaje
import Control.Monad
import Text.Printf
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
main = forM_ [0..35] $ \i ->
printf "n=%d => %d\n" i (fib i)
* Llamamos a los módulos Control.Monad y Text.Printf, el primero nos da acceso a acciones abstractas datatipadas, mientras que el segundo al conocido printf que nos permite imprimir texto con formato
* Definimos la función fib indicando que recibiremos un enetero (Int) y recibiremos otro (Int)
* Dentro de fib manipulamos los datos y establecemos que n es el valor a recibir y el resultado de su operación el (valor) de retorno
* Emplean main establecemos un bloque que puede invocar al módulo, que gracias a la función forM_ (parte de Control.Monad) nos permite iterar sobre los 35 primeros primos, cada valor va pasando al printf que como argumentos tiene la llamada al módulo fib.
* Grabamos el archivo con extensión .hs y salimos al intérprete (fibo.hs)
Probaremos nuestro ejercicio directamente desde el intérprete Haskell, con lo cual desde el mismo directorio donde grabamos nuestro archivo lo invocamos con ghci. Dentro de este, empleamos una comprensión de listas para invocarlo:
ghci fibo.hs
[fib x | x <-[0..35]]
*Al cabo de unos segundos aparecerá una lista con la secuencia fibonacci
*Salimos con [CTRL]+Z
El problema de lo que acabamos de hacer es que nos ha tomado algunos valiosos segundos y eso NO ES ACEPTABLE, tratemos -ahora- de compilar la aplicación
ghc -o fibo fibo.hs
*Esperamos unos momentos y ya tenemos un ejecutable listo para correr
./fibo
*Estoy empleando la aplicación time para medir los tiempos de ejecución y consumo de CPU, lo pueden instalar con
sudo apt-get install time
*Lo empleamos así
/usr/bin/time ./fibo
*Nos presenta el resultado de los 35 números en 3.04 seg y con un consumo de CPU de 99%
Eso es rápido? Contra qué lo comparamos? Bueno, ahora lo entenderán porque voy a hacer lo mismo con un programa en
Python
def fib(n):
if n == 0 or n == 1:
return n
else:
return fib(n-1) + fib(n-2)
for i in range(36):
print "n=%d => %d" % (i, fib(i))
*Lo grabamos como fibo.py y ejecutamos
/usr/bin/time python fibo.py
*El resultado se obtuvo en 18.79 seg y con un consumo de CPU de 100%
Ahora ya tenemos una referencia, Haskell ejecutó la aplicación compilada 6 veces más rápido que en Python, pero digamos qué hay de C?
#include
int main()
{
unsigned int i=0,j=0,sum=1,num=0;
num=9227466;
while(sum < num)
{
printf("%d ",sum);
i=j;
j=sum;
sum=i+j;
}
}
*Los grabamos como fibo.c,compilamos y ejecutamos
gcc -o fibo-c fibo.c
/usr/bin/time ./fibo-c
*El resultado se obtuvo en 0.0 seg y con un consumo de CPU de 0%
"Pero mejor programemos en C", dirán algunos de ustedes, pero quienes desarrollamos sabemos que hacerlo así implicaría manejar engorrosos procesos que harán muy largo y lento el trabajo y es por ello que existen otros lenguajes de programación: hacer nuestra vida más simple (cuando es posible). Ahora, tratemos de hacer algo más interesante aún: elevar el rendimiento de nuestra aplicación Haskell hasta menos de un segundo; Gracias a que tengo 2 núcleos puedo dividir las operaciones que realiza el programa para que cada uno de ellos cumpla una tarea y así acelerar el proceso.
import Control.Monad
import Text.Printf
import Control.Parallel
fib :: Int -> Int
fib 0 = 0
fib 1 = 1
fib n = l `pseq` r `pseq` l+r
where
l = fib (n-1)
r = fib (n-2)
main = forM_ [0..35] $ \i ->
printf "n=%d => %d\n" i (fib i)
*El programa es el mismo que el original, pero se ha agregado el módulo Control.Parallel y dentro de fib n se ha invocado a la función pseq para que cada argumento pase por un proceso en cada núcleo
*Lo grabamos como fibo-m.hs, compilamos y ejecutamos
ghc -o fibo-m -O2 --make fibo-m.hs -threaded
/usr/bin/time ./fibo-m
*El resultado ahora es de 0.41 seg y con un consumo de CPU de 102%