Attitude

Blog image.Blog image.

Imágenes en el blog

Iba a introducir imágenes en el blog y he querido escribir una pequeña función que hace más sencillo introducir fácilmente las imágenes con la ruta por defecto, y, si procede, un enlace para las mismas. La función queda como sigue:

(defun blog-img (img-file &key alt anchor title params)
  (let* ((param-list
          (cons `(src . ,(format nil "~A/~A" *base-img-url* img-file))
                (cons `(alt . ,(or alt "Blog image.")) ; alt is obligatory
                      (when title `((title . ,title))))))
         (param-list-1 (append param-list params))
         (img-html (img param-list-1)))
    (if anchor
        (a `((href . ,anchor)) img-html)
        img-html)))

¿No es bonito? En particular me gusta el uso del seudoquote. Las funciones img y a generan el HTML para las imágenes y para los enlaces, respectivamente. Un ejemplo de uso de esa función sería:

(blog-img "abc.jpg" :alt "Alt text" :params '((:width . 500)))

donde se elige el fichero img/abc.jpg con un texto alternativo y con el conjunto de parámetros adicionales, entre ellos el ancho de la imagen. Si se especifica un elemento anchor el código que se genera es el siguiente:

(blog-img "abc.jpg" 'anchor "http://wherever.com"  'alt "bah" 'params '(('width . 500)))
<A HREF="http://wherever.com"><IMG SRC="img/abc.jpg" ALT="bah" WIDTH="500"></IMG></A>

Estadísticas de tiempo de generación del blog

De cara a optimizar la generación de las páginas del blog con multiprogramación, he querido registrar el tiempo que tarda en ejecutar la generación en el ordenador que se genera, para compararla después con la optimización. Para mi sorpresa, la mayor parte del os 8 segundos que lleva la generación se la lleva el leer y compilar el fichero Lisp que contiene las entradas antiguas (1.3MB), mientras que la generación de todas las páginas no tarda más de 4 segundos:

[dsevilla@neuromancer:~/svn/blog]$ sbcl --script packages.lisp
Doing pre-calculations...
Generating index page...
Evaluation took:
  0.116 seconds of real time
  0.120000 seconds of total run time (0.080000 user, 0.040000 system)
  [ Run times consist of 0.040 seconds GC time, and 0.080 seconds non-GC time. ]
  103.45% CPU
  325,871,901 processor cycles
  63,697,264 bytes consed

Generating post pages...
Evaluation took:
  0.408 seconds of real time
  0.410000 seconds of total run time (0.270000 user, 0.140000 system)
  [ Run times consist of 0.010 seconds GC time, and 0.400 seconds non-GC time. ]
  100.49% CPU
  1,144,029,612 processor cycles
  167,055,120 bytes consed

Generating categories pages...
Evaluation took:
  0.074 seconds of real time
  0.070000 seconds of total run time (0.070000 user, 0.000000 system)
  94.59% CPU
  209,026,050 processor cycles
  39,985,040 bytes consed

Generating archives pages...
Evaluation took:
  0.086 seconds of real time
  0.090000 seconds of total run time (0.090000 user, 0.000000 system)
  104.65% CPU
  240,093,459 processor cycles
  43,278,720 bytes consed

Generating RSS...
Evaluation took:
  0.084 seconds of real time
  0.080000 seconds of total run time (0.080000 user, 0.000000 system)
  [ Run times consist of 0.020 seconds GC time, and 0.060 seconds non-GC time. ]
  95.24% CPU
  236,784,003 processor cycles
  35,828,208 bytes consed

Esto hace que la optimización, como máximo, sólo pueda reducir esos 0,4 segundos que tarda la generación. Aún así lo intentaré como un ejercicio de programación. La otra idea será ver optimizar el proceso de carga quizá a través de pre-compilación de los ficheros .lisp. Por cierto, para que luego digan que los lenguajes interpretados son lentos... 1 segundo en generar 34MB de ficheros de texto.

La última sorpresa... Por casualidad he probado clisp... Bien, aquí la carga de los ficheros .lisp es instantánea, y la ejecución es incluso más rápida (diría incluso increíblemente rápida:

[dsevilla@neuromancer:~/svn/blog]$ clisp packages.lisp
Doing pre-calculations...
Generating index page...
Real time: 0.186066 sec.
Run time: 0.19 sec.
Space: 24960736 Bytes
GC: 13, GC time: 0.03 sec.
Generating post pages...
Real time: 0.708989 sec.
Run time: 0.69 sec.
Space: 83706152 Bytes
GC: 36, GC time: 0.06 sec.
Generating categories pages...
Real time: 0.051339 sec.
Run time: 0.05 sec.
Space: 11157832 Bytes
GC: 5, GC time: 0.0 sec.
Generating archives pages...
Real time: 0.249124 sec.
Run time: 0.25 sec.
Space: 13408648 Bytes
GC: 6, GC time: 0.02 sec.
Generating RSS...
Real time: 0.891364 sec.
Run time: 0.88 sec.
Space: 20991880 Bytes
GC: 9, GC time: 0.05 sec.

Comparando el tiempo de ejecución visto por el usuario:

[dsevilla@neuromancer:~/svn/blog]$ time clisp packages.lisp
real	0m2.320s
user	0m1.970s
sys	0m0.310s
[dsevilla@neuromancer:~/svn/blog]$ time sbcl --script packages.lisp
real	0m10.405s
user	0m9.940s
sys	0m0.440s

Esto es, ¡5 veces más rápido en general clisp que sbcl! Sin embargo, mirando los datos de cada parte, hay resultados muy extraños e inconsistentes. Por ejemplo, clisp tarda casi un segundo en generar RSS, mientras que sbcl tarda 0,08 segundos (un orden de magnitud menos). Estudiaré el código para ver dónde puede estar el problema, pero por ahora, usaré clisp para generar el blog, aunque use sbcl, con el magnífico entorno Slime para Emacs para seguir programando y probando.

Multiprocesamiento para generar el blog

Siguiendo este enlace voy a intentar añadir multiprocesamiento a la generación del blog para acelerarlo. No va a ser tan sencillo como debería ser, por ejemplo, debería existir, como en Clojure, un parallel map pero la verdad es que no hay, sólo hilos tradicionales... La ventaja, sin embargo, será grande, ya que todas las páginas se pueden generar en paralelo.

Entradas del blog antiguo disponibles

No sin algo de trabajo, salvando las idiosincrasias de SQL, donde tenía alojado mi anterior blog con la estructura de base de datos de Wordpress, (por ejemplo, las comillas simples en SQL son '', y no \') y de Common-Lisp (por ejemplo, no acepta caracteres especiales como \n ni \r), he conseguido añadir todas las entradas del anterior blog. Ha sido sorprendentemente automático, dadas las conversiones pertinentes (no muchas).

Pero lo que más me ha sorprendido es que el blog actualmente, al ser generado estáticamente, ocupa 34 MBytes de datos, está compuesto por 1393 ficheros HTML, y tarda unos 5 segundos en generarse. No está mal para ser un lenguaje interpretado. Y podría ser mucho más rápido si supiera optimizarlo bien, y arreglara algún que otro algoritmo que podría hacerse más rápido. También si pre-compilara el código en vez de hacerlo cada vez que se genera el blog (esto lo añadiré al script de generación, que recompile los ficheros sólo si han cambiado).

Al ser tantas entradas, he tenido que añadir una opción de dividir todos las entradas en páginas, a un número de 50 entradas por página (de la página principal salen 13 subpáginas). Ha sido algo complicado integrar la generación de várias subpáginas en lo que ya tenía, pero no me ha llevado mucho (una media hora). En parte ha sido más complejo porque Common-Lisp no tiene una operación para partir una lista (la de las entradas) eficientemente, y he tenido que hacerla yo. No es muy compleja, pero tampoco trivial si quieres hacerla eficiente.

Añadido colorización de código con google-code-prettify

Pues no ha sido complicado. Simplemente he seguido las instrucciones del README de la página de google-code-prettify y ya está.

Fiestas en Mula

Me alegro de que mi amigo Pedro Aurelio continúe su blog. En la última entrada de su blog, aparte del sentimiento religioso, que en mi caso es nulo, sí que echo de menos sentir el sonido de mi pueblo, poder pasear tranquilamente por él... ¡No dudéis en acercaros a Mula del 19 al 25 de septiembre!

Y las páginas de los tags

Sólo por curiosidad, he aquí cómo está implementada la generación de los links con diferente tamaño del sidebar:

(defun categories-links ()
  (if *categories-links*
      *categories-links*
      (setf *categories-links* (multiple-value-bind (max-n-posts min-n-posts)
          (loop for c being the hash-values in *posts-for-category*
             maximizing (car c) into max
             minimizing (car c) into min
             finally (return (values max min)))
        (apply #'concatenate 'string
                (loop for k being the hash-keys in *posts-for-category*
                   using (hash-value v)
                   collect (format nil "<a href=\"category-~A.html\"
                             title=\"~A topic~:*~p\" rel=\"category tag\"
                             style=\"font-size: ~Apx;\">~3:*~A</a> "
                             (string-downcase k)
                             (car v)
                             (+ 9 (round
                                   (/ (- (car v) min-n-posts)
                                      (/ (- max-n-posts min-n-posts) 10)))))))))))

Y eso sin contar con el cálculo de *posts-for-category*.

Ya funcionan los archivos

No un gran logro, pero ya funcionan.

¡Blog online!

Pues por ahora no funciona casi nada, pero el blog ya se auto-genera, lo cual está muy bien. Por ahora se generan las páginas de cada entrada (sin comentarios por ahora), y las categorías, también cada una en su página. No genera por ahora ni RSS ni Atom, pero eso es sencillo una vez que lo tengo todo funcionando. Como cada entrada es un fichero de texto, añadiré también macros de Emacs para generar enlaces con macros...

¡Primera entrada!

Esta es la primera entrada de este nuevo blog cuyo generador está escrito en Common-Lisp. ¿Lisp? Sí, más de 50 años después sigue vivo, y sinceramente, es una maravilla aprenderlo y usarlo. Iré mostrando en el futuro cómo se genera.