Comparación entre schemes
Llevo un tiempo con el blog en barbecho. No es que no hiciera nada entre medias, estoy con mis cosas y cuando abandonas la costumbre de escribir en un medio que requiere periodicidad, parece que cuesta más retomarlo de nuevo y poco a poco lo vas dejando más, hasta que te das cuenta que ha pasado ya, quizá, demasiado tiempo desde el último post.
Para no procrastinar más escribo este artículo de una de las cosas que voy haciendo: Aprender scheme.
Aprendiendo scheme
Llevo ya un tiempo queriendo aprender programación funcional y concretamente scheme. De hecho estoy trasteando con dos sabores del lenguaje sin terminar de decidirme por uno de los dos. Pensé que hacer una prueba de trabajo entre las dos me ayudaría a tomar esa decisión, pero me encuentro en la misma situación que antes. No puedo establecer cuál me gusta más, si Racket o Chicken.
De Racket me gusta su documentación y las facilidades que pone al aprendizaje. Viene con un entorno de programación para un entorno gráfico que facilita el depurado y la ejecución de scripts. De Chicken me gusta la optimización y que he podido compilarlo también en android en el entorno termux, así que puedo tener el mismo programa funcionando en mi ordenador y en mi tablet o teléfono sin necesidad de modificar una coma. Sin embargo, Racket supera con mucho la documentación que viene con Chicken, que tampoco es mala, pero está escrita para quien ya sabe el lenguaje.
Banco de pruebas
En realidad no es un banco de pruebas como tal, pues sólo he trabajado con un script muy normal, que hace cálculos reiterativos que pueden poner a prueba los tiempos de ejecución. Esperaba, desde el principio que Chicken obtuviera mejores resultados de ejecución, no en vano en su web lo primero que encuentras es:
CHICKEN is a compiler for the Scheme programming language. It produces portable and efficient C and supports the R5RS and R7RS (work in progress) standards, and many extensions.
Es decir, el objetivo de Chicken proveer un compilador de scheme eficiente, mientras que el objeto de Racket es:
Racket is a general-purpose programming language as well as the world’s first ecosystem for developing and deploying new languages.
Además, para instalar Chicken en mi máquina bajé el código fuente y lo compilé. Por tanto está optimizado para mi máquina mientras que Racket es un paquete distribuido genéricamente para OpenSuse. No daré más información sobre ellos, pues sus páginas web son bastante ilustrativas de qué persiguen y hacen cada uno de ellos. El caso es que aunque sean ambos dos sabores de scheme parece como comparar peras con manzanas.
El(los) código(s) de marras
Al principio me las prometí muy felices haciendo un script y poniéndolo en marcha conseguir los datos de ejecución. La primera versión del código es la siguiente:
; Calcular pi con la fórmula de Bailey-Borwein-Plouffe (define (ochok k) (* 8 k)) (define (termino k) (let ([n (ochok k)]) (- (/ 4 (+ n 1)) (/ 2 (+ n 4)) (/ 1 (+ n 5)) (/ 1 (+ n 6))))) (define (bbp i) (* (/ 1 (expt 16 i)) (termino i))) (define (calcular-pi precision) (if (< precision 0) 0 (+ (bbp precision) (calcular-pi (- precision 1))))) (display (calcular-pi 10000)) (newline)
Como se puede ver utilizo la fórmula de Bailey-Borwein-Plouffe para calcular pi. Esta fórmula está mejor explicada en su correspondiente artículo de la wikipedia de lo que yo podría explicarla aquí, no entraré, pues, en esos detalles.
El código funcionó con ambos scheme sin modificar un paréntesis. Sin
embargo, las primeras pruebas me parecieron tan escandalosamente
dispares que me entretuve en pensar que quizá estaba siendo injusto
con uno de los entornos. La salida de cada uno de los schemes era
muy distinta: Chicken devolvía un escueto 3.14159265358979 y ya
está, mientras Racket devolvía un cociente de números enormes que
tardaban segundos en ser escritos en pantalla. La única línea que se
añadió en el fichero pi-decimales.rkt
que no está en el
pi-decimales.scm
fue la definición del lenguaje que necesita
Racket para ejecutar el código:
#lang racket/base
Sin embargo, la diferencia que había en la salida de los dos
scripts, el .rkt
y el .scm
seguía pareciéndome muy
significativa y quizá debía corregirlo. Lo que hice fue forzar a
Racket para que calculara también pi de forma inexacta y para
eso utilicé el siguiente código:
#lang racket/base ; Calcular pi con la fórmula de Bailey-Borwein-Plouffe (define (ochok k) (exact->inexact (* 8 k))) (define (termino k) (let ([n (ochok k)]) (- (/ 4 (+ n 1)) (/ 2 (+ n 4)) (/ 1 (+ n 5)) (/ 1 (+ n 6))))) (define (bbp i) (* (/ 1 (expt 16 i)) (termino i))) (define (calcular-pi precision) (if (< precision 0) 0 (+ (bbp precision) (calcular-pi (- precision 1))))) (display (calcular-pi 10000)) (newline)
Básicamente es el mismo código, pero en la función ochok
se fuerza
el cálculo con exact->inexact
. El resto de cálculos arrastran esa
conversión y el resultado final es el esperado 3.141592653589793.
Además me encontré con otra diferencia fundamental a la hora de hacer
los compilados: el tamaño. Y también es normal que Chicken genere
compilados más pequeños, pero la diferencia es amplísima, porque
entiendo que mete en el ejecutable todas las librerías (sean usadas o
no) y el intérprete del código, mientras en Chicken no. Al final, se
pudo optimizar algo más el tamaño utilizando como lenguaje
racket/base
en lugar de racket
a secas.
Resultados
Como son pruebas de andar por casa, no he tenido en cuenta cosas como
la gestión de memoria y los tiempos sólo los he medido con el comando
time
. Por tanto, estos resultados deben ser tomados como lo que son:
pruebas de aficionado.
ejecución | tamaño | t real | t user | t sys |
---|---|---|---|---|
pi-chicken | 27 Kb | 0,021s | 0,019s | 0,002s |
pi-racket | 6,7 Mb | 0,490s | 0,413s | 0,078s |
pi-raco-exact | 0,9 Mb | 36,200s | 36,095s | 0,126s |
pi-raco-inexact | 0,9 Mb | 0,192s | 0,166s | 0,026s |
interpretado Chicken | 0,046s | 0,041s | 0,005s | |
interpretado Racket exacto | 38,679s | 38,507s | 0,185s | |
interpretado Racket inexacto | 2,834s | 2,724s | 0,114s |
Los tamaños en la ejecución de entornos interpretados se obvian. Los comandos para realizarlos respectivamente son:
time racket -c pi-decimales-exact.rkt
Para la versión exacta de Racket.
time racket -c pi-decimales.rkt
Para la versión inexacta de Racket y por último, para la versión de Chicken se empleó:
time csi -s pi-decimales.scm
Para compilar los distintos ejecutables he utilizado las herramientas
que ambos entornos proporcionan raco
y csc
.
Conclusiones
Bueno, pues los resultados están ahí si sólo buscas velocidad está claro que scheme debes elegir. Sin embargo, no todo es velocidad: si todo fuera velocidad sólo se venderían coches deportivos, pero se ven coches familiares con más frecuencia. Y es el caso en el que me encuentro.
El rendimiento es claramente de Chicken; sin embargo, todo el
entorno que rodea a Racket, especialmente su documentación me hacen
mantenerlo como entorno de aprendizaje y aunque no me encuentro cómodo
con DrRacket (el entorno gráfico que viene con él) puedo utilizarlo
también como entorno interactivo, tanto en consola con el comando
racket
, en entorno gráfico con el comando gracket
o con el mismo
DrRacket, que proporciona editor, entorno interactivo, depurador y
permite también compilar.
También hay varias herramientas que vienen con Chicken, depurador, profiler, etc. En este sentido tampoco viene manco.
No puedo recomendar ninguno de los dos por delante del otro. Es decir, utilizo los dos, a ambos les encuentro ventajas e inconvenientes y voy a seguir utilizándolos, los dos. Mientras se mantiene el código en el ámbito más estándar, el código escrito para uno sirve directamente para el otro. Pues eso: ahí lo dejo.
Comentarios