Notxor tiene un blog

Defenestrando la vida


Compració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

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en Mastodon y en Diaspora con el nick de Notxor.

También se ha abierto un grupo en Telegram, para usuarios de esa red.

Disculpen las molestias.