Elección del lenguaje
Introducción
En este apartado se hará una comparación lo más detallada posible de cada uno de los lenguajes estudiados y por último se elegirá el más adecuado.
Está previsto estudiar los siguientes lenguajes:
Tcl
Python
Perl
Ruby
LUA
PHP
JSP
JavaScript
ASP
AngelScript
Squirrel
Tcl
Informacion: http://es.wikipedia.org/wiki/Tcl
De Tcl se han estudiado tres versiones
Jim
Jim es un intérprete que ocupa apenas 150KB y entiende la mayoría de la sintaxis del Tcl
wget http://www.hping.org/jim/jim-hourly.tar.gz tar xvfp jim-hourly.tar.gz cd jim make CC="powerpc-linux-gcc" CFLAGS="-Os" jim sudo cp jim /NFSroot/bin/ sudo cd /NFSroot/bin
Sin embargo, ha sido descartado por no tener soporte para sockets.
Tcl 8.5.7
A continuación se prefirió investigar sobre el intérprete completo y no la versión reducida, el primero en ser estudiado fue el 8.5.7, que era la última versión.
wget http://prdownloads.sourceforge.net/tcl/tcl8.5.7-src.tar.gz tar xpvf tcl8.5.7-src.tar.gz cd tcl8.5.7/unix ./configure --disable-symbols --disable-load --disable-shared --disable-langinfo make CC="powerpc-linux-gcc" CFLAGS_OPTIMIZE="-Os"
Sin embargo no fue posible conseguir que compilara correctamente y corriera sin problemas en el router, por lo tanto fue descartado.
Tcl 7.5
Por último se estudió esta versión, que sí fue posible hacerla correr perfectamente en la plataforma.
wget http://sourceforge.net/projects/tcl/files/Tcl/7.5/tcl7.5.tar.Z/download tar xvzf tcl7.5.tar.Z cd tcl7.5/unix /*Cambiar ./configure y ../generic/tclPosixStr.c*/
Antes de compilar es necesario cambiar el fichero ./configure por: configure_tcl7.5.txt y el fichero ../generic/tclPosixStr.c por: tclposixstr_tcl7.5.txt
CC="powerpc-linux-gcc" ./configure --disable-symbols --disable-load --disable-shared --disable-langinfo make CFLAGS="-Os" make prefix="/NFSroot" exec_prefix="/NFSroot" install-binaries make prefix="/NFSroot" exec_prefix="/NFSroot" install-libraries
Esta versión ocupa 720KB, tiene todas las funcionalidades del Tcl y dispone de sockets.
Conclusión
Se puede ver claramente cuál es el mejor intérprete de los estudiados, según la siguiente tabla comparativa:
| Intérprete | Compilación | Ejecución | Tamaño en disco | Sockets |
|---|---|---|---|---|
| Jim | Si | Si | 150KB | No |
| Tcl8.5.7 | No | No | ? | ? |
| Tcl7.5 | Si | Si | 720KB | Si |
Como vemos, de los estudiados el Tcl7.5 es el único con soporte para sockets que funciona en el router, y aunque el espacio de disco ocupado sea notablemente superior al Jim, es preferible utilizarlo por homogeneidad y facilidad para implementar aplicaciones.
Python
Informacion: http://es.wikipedia.org/wiki/Python
Se ha elegido un intérprete de Python llamado PyMite, que ofrece un subconjunto del lenguaje Python ocupando muy pocos recursos. Pymite funciona interpretando un script en Python a través de un lanzador hecho en C, el resultado es un ejecutable precompilado. Se ha probado y funciona bastante bien, aunque es algo limitado y no tiene soporte para sockets, sin embargo, resulta evidente de que se ha perdido la ventaja de ser un lenguaje de scripting al tener que compilarse. Por estas razones, ha quedado descartado.
wget http://python-on-a-chip.googlecode.com/files/pymite-08.tar.gz tar xvpf pymite-08.tar.gz cd pymite-08 cp -r src/platform/desktop src/platform/powerpc cd src/platform/powerpc
Creamos un programa de ejemplo, descomprimimos los ficheros de Prueba en el directorio actual (src/platform/powerpc). Como se puede ver, es un fichero en main.c que llama a una funcion en un script de python main.py. A continuación compilamos el ejecutable:
make CC=”powerpc-linux-gcc”
Ahora si colocamos main.out en el router podremos ver que la llamada y el resultado son correctos.
Perl
Informacion: http://es.wikipedia.org/wiki/Perl
Los intérpretes de Perl ocupan relativamente bastante espacio en disco, fue por ello que se buscó un intérprete de Perl reducido: microperl. Microperl apenas ocupa 706KB una vez compilado y cuenta con un subconjunto de la funcionalidad de Perl, el problema es que no cuenta con sockets, que es algo básico en lo que se quiere conseguir, para instalarlo:
wget http://www.perl.com/CPAN/src/perl-5.7.0.tar.gz tar xvpf perl-5.7.0.tar.gz cd perl-5.7.0/
Descomprimir config_perl5.7.tar en el directorio
make -f Makefile.micro
Con esto se creará un ejecutable llamado “microperl” en el directorio actual, si lo copiamos al router funcionará correctamente.
Ruby
Información: http://es.wikipedia.org/wiki/Ruby
Para compilar las fuentes del Ruby, necesitamos tenerlo previamente instalado:
sudo apt-get install ruby
Ahora podemos pasar a configurar, compilar e instalar el Ruby, primero obtenemos las fuentes:
wget http://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p243.tar.bz2 tar xvpf ruby-1.9.1-p243.tar.bz2 cd ruby-1.9.1-p243/
Creamos un directorio que contendrá la instalación y configuramos el Ruby:
mkdir local CC=powerpc-linux-gcc CXX=powerpc-linux-g++ LD=powerpc-linux-gcc ac_cv_func_getpgrp_void=no ac_cv_func_setpgrp_void=yes ./configure --host=powerpc-linux --disable-largefile --prefix=<ruta>/local
Esto es lo que significan las opciones de configuración:
- CC, CXX, LD: especificamos las herramientas de compilación cruzada.
- ac_cv_func_getpgrp_void=no, ac_cv_func_setpgrp_void=yes: estos parámetros son necesarios para la compilación cruzada, sin ellos realiza una serie de comprobaciones que fallan al tratarse de una compilación cruzada.
- –host=powerpc-linux: para indicar que se trata de una compilación cruzada.
- –disable-largefile: para deshabilitar los ficheros >2GB que no soporta uClibc.
- –prefix=<ruta>/local: ruta donde se instalará el Ruby (tiene que ser una ruta absoluta)
Una vez configurada la compilación, procedemos a hacerla, pero antes es necesario modificar un fichero de configuración del módulo de sockets. El problema es que para configurarlo, se intentan crear y ejecutar algunos programas para comprobar que se ejecutan correctamente, pero al ser compilación cruzada no se puede ejecutar en la máquina de trabajo y falla al compilar los sockets. La solución es cambiar las pruebas de ejecución por simples pruebas de compilación, utilizamos el mandato 'sed' para buscar y reemplazar en el fichero:
sed 's/try_run/try_compile/g' ext/socket/extconf.rb > ext/socket/extconf2.rb mv ext/socket/extconf2.rb ext/socket/extconf.rb
Ahora que todo está configurado correctamente, procedemos a compilar e instalar:
make optflags=-Os make install cd local powerpc-linux-strip lib/ruby/1.9.1/powerpc-linux/*.so powerpc-linux-strip bin/ruby tar -cf ruby.tar bin/ruby lib/ruby/1.9.1/powerpc-linux/*.so lib/ruby/1.9.1/powerpc-linux/*.rb
Para portarlo al router basta con copiar el archivo ruby.tar en el directorio raíz del router y descomprimir los ficheros.
Esta compilación del Ruby ocupa 2,6MB. En principio puede parecer muy pesada, pero es importante resaltar que es posible implementar muy fácilmente cosas como el servidor HTTP que será necesario más adelante.
Lua
Información: http://es.wikipedia.org/wiki/Lua
Ocupa 800KB
wget http://www.lua.org/ftp/lua-5.1.4.tar.gz tar xvpf lua-5.1.4.tar.gz cd lua-5.1.4
Se han hecho cambios en los ficheros “Makefile” y “src/Makefile”, para que Lua se compile como una biblioteca dinámica (liblua51.so.0) y no estática como se compila por defecto. La razón de este cambio es porque Lua no dispone de sockets, hay que instalar un paquete adicional llamado LuaSocket para poder usarlos, y dicho paquete espera una biblioteca dinámica.
Descargamos los archivos y los sobreescribimos:
wget http://laurel.datsi.fi.upm.es/_media/proyectos/teldatsi/makefile_lua5.1.4.tar tar xvpf makefile_lua5.1.4.tar
Compilamos y preparamos la instalación:
make posix.so local TO_BIN="lua" TO_LIB="liblua51.so.0" tar -cf lua.tar bin include lib share
Esto nos habrá creado una estructura de directorios en el fichero “lua.tar” que se debe descomprimir en la raíz del router.
LuaSocket
Ocupa 138KB
El siguiente paso es instalar el paquete de sockets. Es importante destacar que este paquete tiene que compilarse contra las bibliotecas de Lua, es decir, que nuestras herramientas de compilación cruzada deben disponer del código que hemos generado. La forma más facil de hacerlo es descomprimir el tar que hemos creado (lua.tar) en el directorio <Ruta>/buildroot/build_powerpc/staging_dir/usr
Una vez hemos hecho eso, podemos compilar LuaSocket:
wget http://luaforge.net/frs/download.php/2664/luasocket-2.0.2.tar.gz tar xvpf luasocket-2.0.2.tar.gz cd luasocket-2.0.2
Antes de compilar, hay que cambiar el fichero “config” por este, tiene modificaciones para utilizar el compilador cruzado y para que la instalación sea local. Además se han incorporado algunos flags para que optimizar el tamaño en disco.
make make install
Esto nos habrá creado un directorio “share” y otro “lib” en src/, ahora lo instalamos en el router
sudo mkdir -p /NFSroot/usr/local/lib/lua/5.1 sudo mkdir -p /NFSroot/usr/local/share/lua/5.1 sudo cp -r src/share/* /NFSroot/usr/local/share/lua/5.1/ sudo cp -r src/lib/* /NFSroot/usr/local/lib/lua/5.1/
ex API
Extension Proposal es una API que añade funcionalidades a LUA, como por ejemplo una función sleep. Es necesaria para algunas pruebas que se realizarán en las siguientes secciones.
cvs -d :pserver:anonymous@cvs.luaforge.net:/cvsroot/lua-ex login cvs -d :pserver:anonymous@cvs.luaforge.net:/cvsroot/lua-ex checkout lua-ex cd lua-ex/
Es necesario colocar las siguientes variables en el fichero de configuración:
echo POSIX_SPAWN= -DMISSING_POSIX_SPAWN > conf echo EXTRA= posix_spawn.o >> conf
Compilamos e instalamos:
make linux CC='powerpc-linux-gcc -fPIC' sudo cp posix/ex.so /NFSroot/usr/local/lib/lua/5.1/
Conclusión
Lua + LuaSocket son una muy buena opción, ya que el resultado es un lenguaje de scripting bastante potente y con capacidad para sockets, todo por menos de 1MB.
PHP
Informacion: http://es.wikipedia.org/wiki/PHP
De este lenguaje se han probado 2 versiones, la 5.3.0 y la 5.2.10
PHP 5.3.0
wget http://es2.php.net/get/php-5.3.0.tar.bz2/from/this/mirror tar xvpf php-5.3.0.tar.bz2 cd php-5.3.0/ CC="powerpc-linux-gcc" ./configure --enable-sockets --disable-xml --disable-xmlreader --disable-xmlwriter --disable-libxml --disable-dom --disable-simplexml --without-pear --with-libdir=/buildroot/buildroot/build_powerpc/staging_dir/usr/lib
Los argumentos pasados al configure son para deshabilitar módulos que dependen de la librería XML, que no hemos incorporado al sistema de ficheros del router. Ahora nos queda compilar:
make CFLAGS_CLEAN='-Os -fvisibility=hidden -g -DSQLITE_DISABLE_LFS' LDFLAGS='-ldl'
Los argumentos del make son los siguientes:
CFLAGS_CLEAN='-Os -fvisibility=hidden -g -DSQLITE_DISABLE_LFS'-Os: para optimizar el tamaño
-fvisibility=hidden y -g: estos estaban ya predefinidos en el Makefile, los conservamos.
-DSQLITE_DISABLE_LFS: esto es necesario para desactivar el soporte de ficheros mayores de 2GB, cosa que no soporta la uClibc
Llegados a este punto el make falla por lo siguiente:
main/php_ini.o: In function `php_load_php_extension_cb': main/php_ini.c:330: undefined reference to `php_load_extension'
Según información consultada en Internet, se trata de un fallo de esta versión para compilaciones cruzadas, por lo que hemos vuelto a una versión anterior, la 5.2.10.
PHP 5.2.10
Para esta versión se han seguido los mismos pasos que para la 5.3.0, aunque añadiendo algunos flags para ahorrar espacio.
wget http://es2.php.net/get/php-5.2.10.tar.bz2/from/this/mirror tar xvpf php-5.2.10.tar.bz2 cd php-5.2.10/ mkdir instalacion
Se ha añadido la opción –disable-all y –disable-cli para optimizar el espacio ocupado.
CC="powerpc-linux-gcc" ./configure --disable-all --enable-sockets --disable-xml --disable-xmlreader --disable-xmlwriter --disable-libxml --disable-dom --disable-simplexml --without-pear --disable-cli --with-libdir=/buildroot/buildroot/build_powerpc/staging_dir/usr/lib make CFLAGS_CLEAN='-Os -fvisibility=hidden -g -DSQLITE_DISABLE_LFS' LDFLAGS='-ldl'
Lo instalamos en una caprta local que hemos creado:
make prefix=instalacion exec_prefix=instalacion install
En este momento ya tenemos el ejecutable, aunque es excesivamente grande para poder portarlo, así que utilizamos la herramienta strip y lo copiamos al sistema de ficheros:
powerpc-linux-strip instalacion/bin/php sudo cp instalacion/bin/php /NFSroot/bin/
Esta versión de PHP ocupa 1,8MB, funciona bastante bien y tiene soporte para sockets.
Conclusión
| Versión | Compilación | Ejecución | Tamaño en disco | Sockets |
|---|---|---|---|---|
| PHP 5.3.0 | No | No | ? | ? |
| PHP 5.2.10 | Si | Si | 1,8MB | Si |
Evidentemente de entre las 2 versiones analizadas, la 5.2.10 es la que será tomada en cuenta para futuros análisis.
JSP
Información: http://java.sun.com/javase/downloads/embedded.jsp
El JSP necesita la máquina virtual de Java para ejecutarse, entrando en la web de Sun Microsystems se puede ver que hay versiones de ésta para sistemas empotrados, pero todas ellas ocupan 32MB o más, por lo que JSP queda descartado como opción.
JavaScript
Información: http://es.wikipedia.org/wiki/JavaScript
Se ha probado SpiderMonkey, que es la implementación de JavaScript de Mozilla.
Instalación:
wget http://ftp.mozilla.org/pub/mozilla.org/js/js-1.8.0-rc1.tar.gz tar xvpf js-1.8.0-rc1.tar.gz cd js/src
La compilación de este intérprete se realiza a través del fichero Makefile.ref, que lo que hace es crear ejecutables intermedios, ejecutarlos, y obtener sus salidas, que son ficheros de configuración, como entradas para otros ejecutables. Sin embargo, esto ocasiona un problema al compilar de forma cruzada, porque el código compilado no se puede ejecutar en la máquina que compila, y por lo tanto produce errores durante el proceso. Esto se ha solventado moviendo y ejecutando estos ejecutables en el router para luego introducir artificialmente los ficheros de configuración generados. Para facilitar el proceso, se ha cambiado el fichero Makefile.ref y se han incluído los ficheros de configuración aquí.
wget http://laurel.datsi.fi.upm.es/_media/proyectos/teldatsi/js_config.tar tar xvpf js_config.tar make CC=powerpc-linux-gcc LD=powerpc-linux-ld OPTIMIZER=-Os XCFLAGS=-DHAVE_VA_LIST_AS_ARRAY BUILD_OPT=1 XLDFLAGS=-ldl -f Makefile.ref powerpc-linux-strip Linux_All_OPT.OBJ/js sudo cp Linux_All_OPT.OBJ/js /NFSroot/bin/
Esta implementación ocupa apenas 600KB, pero a pesar de ser un lenguaje muy potente, carece de sockets.
ASP
Existe un proyecto, el proyecto mono (http://mono-project.com/Main_Page), que permite ejecutar aplicaciones hechas en ASP.NET (ya compiladas). No se puede implementar un lenguaje de scripting.
AngelScript
Información: http://en.wikipedia.org/wiki/AngelScript
AngelScript es una biblioteca diseñada para poder comunicar aplicaciones hechas en C/C++ con scripts externos a ellas.
wget http://www.angelcode.com/angelscript/sdk/files/angelscript_2.17.2.zip unzip angelscript_2.17.2.zip cd sdk/angelscript/projects/gnuc
Hay que hacer cambios en el fichero 'makefile' en la variable SRCNAMES para compilar sólo las llamadas para la arquitectura PowerPC (as_callfunc_ppc.cpp), así es como debería quedar el makefile.
make CXX=powerpc-linux-gcc CXXFLAGS='-g -Wall -fPIC -Os' SHARED=1
Creamos una estructura de directorios local que va a contener la biblioteca.
mkdir local mkdir local/lib mkdir local/include make LOCAL=local SHARED=1 COPIER=cp install
Con esta biblioteca se debe compilar un fichero en C/C++. Aún no se ha conseguido.
Squirrel
Información: http://en.wikipedia.org/wiki/Squirrel_(programming_language)
Squirrel es un lenguaje de programación basado en Lua y C.
wget http://sourceforge.net/projects/squirrel/files/squirrel2/squirrel%202.2.3%20stable/squirrel_2.2.3_stable.tar.gz/download tar xvpf squirrel_2.2.3_stable.tar.gz
El directorio que hemos descomprimido viene sin permisos, así que se los damos.
chmod +rwxrwxrwx -R SQUIRREL2/ cd SQUIRREL2/
Los makefiles no vienen preparados para hacer una compilación cruzada, así que se tiene que cambiar el compilador predefinido (gcc) por el compilador cruzado, para facilitar la labor, se pueden descargar los makefiles aquí.
wget http://laurel.datsi.fi.upm.es/_media/proyectos/teldatsi/squirrel_conf.tar tar xvpf squirrel_conf.tar make sq32
Ya tenemos el intérprete compilado, pero está enlazado dinámicamente a la librería libstdc++.so.6 que no se porta al sistema de ficheros del router, así que debemos copiarla:
sudo cp <ruta>/buildroot/build_powerpc/staging_dir/usr/powerpc-linux-uclibc/lib/libstdc++.so.6 /NFSroot/lib/ sudo cp bin/sq /NFSroot/bin/
El intérprete apenas ocupa 267KB y el lenguaje es bastante potente, pero carece de sockets.
Elección del lenguaje
Ya se ha hecho un estudio de cada uno de los lenguajes por separado, pero ahora hay que elegir uno de ellos para implementar el sistema de menús y scripts de configuración. En este apartado se irán filtrando lenguajes según una serie de criterios y así poder llegar a la conclusión de cuál elegir.
Criterio 1: Viabilidad
El primer criterio será también el más básico: se descartarán los lenguajes que no se hayan podido portar, tanto porque no cumpla con los requisitos de memoria y espacio en disco del router como porque simplemente no haya sido posible generar el intérprete para la plataforma. También se descartarán aquellas soluciones que no sean lenguajes de scripting como tal.
| Lenguaje | Versión/Intérprete | Viable | Comentario |
|---|---|---|---|
| Tcl | Tcl 7.5 | Si | |
| Python | PyMite | No | Realmente no genera scripts sino un programa compilado |
| Perl | Microperl 5.7.0 | Si | |
| Ruby | Ruby 1.9.1 | Si | |
| Lua | Lua 5.1.4 | Si | |
| PHP | PHP 5.2.10 | Si | |
| JSP | No | Necesita la máquina virtual de Java, ocupa más del espacio disponible | |
| JavaScript | SpiderMonkey 1.8.0 | Si | |
| ASP | No | No implementable en Linux | |
| AngelScript | AngelScript 2.17.2 | No | Parece una biblioteca para generar un programa compilado, no se ha podido generar ninguna prueba |
| Squirrel | Squirrel 2.2.3 | Si |
Quedan por tanto descartados en esta primera fase los lenguajes Python, JSP, ASP y AngelScript.
Criterio 2: Soporte para Sockets
Como se ha dicho anteriormente, se espera que el lenguaje elegido no necesite ninguna herramienta extena como sucede con el sistema actual, que hace uso de Netcat para comunicarse con Quagga. Es necesario que el lenguaje permita implementar sockets.
| Lenguaje | Versión/Intérprete | Sockets | Comentario |
|---|---|---|---|
| Tcl | Tcl 7.5 | Si | |
| Perl | Microperl 5.7.0 | No | |
| Ruby | Ruby 1.9.1 | Si | |
| Lua | Lua 5.1.4 | Si | Incorporando el módulo LuaSocket |
| PHP | PHP 5.2.10 | Si | |
| JavaScript | SpiderMonkey 1.8.0 | No | |
| Squirrel | Squirrel 2.2.3 | No |
En esta segunda fase se han descartado Perl, JavaScript y Squirrel.
Criterio 3: Facilidad para implementar servidor HTTP
Este criterio no será determinante para descartar o no un lenguaje, pero sería muy interesante que contara con esto.
| Lenguaje | Versión/Intérprete | HTTP | Comentario |
|---|---|---|---|
| Tcl | Tcl 7.5 | ¿? | No se ha estudiado aún |
| Ruby | Ruby 1.9.1 | Si | Ruby dispone de un servidor entre sus bibliotecas |
| Lua | Lua 5.1.4 | ¿? | No se ha estudiado aún |
| PHP | PHP 5.2.10 | ¿? | No se ha estudiado aún |
Criterio 4: Pruebas de rendimiento
Se realizará un estudio comparativo de los cuatro lenguajes, que consisten en pruebas de rendimiento en diversas situaciones que se han considerado importantes o interesentantes. EL objetivo es poder medir y comparar el comportamiento de cada uno de ellos para poder elegir el más adecuado.
Las diferentes pruebas se han hecho siguiendo el mismo patrón: se desarrollará el script que implementa la prueba en cada uno de los lenguajes, procurando ser lo más parecidos posible, y un script en Bash será el encargado de ejecutar los scripts mencionados y hacer las mediciones. A continuación, presentará los datos en un fichero Resultado.txt de forma que facilite su exportación en forma de tabla a una herramienta externa (en este caso se ha utilizado OpenOffice.
Estas pruebas se realizarán sobre las tareas que más se necesitarán. Parece interesante conocer el rendimiento de cada lenguaje en el uso de sockets, ya que es imprescindible para dialogar con Quagga y para implementar el servidor http para la interfaz web. Asimismo, se ha detectado que gran parte de las operaciones que realizará el sistema será con manejo de tiras de caracteres y manejo de ficheros. Además, la velocidad de procesamiento general del sistema será igualmente importante estudiarla.
A continuación, se listarán todas las pruebas realizadas, con los resultados obtenidos.
Nota: Los valores de estas pruebas no tienen significado de forma aislada, lo que interesa es comparar los resultados obtenidos en cada lenguaje.
Prueba 1: Impacto en memoria
Esta prueba mide el impacto en memoria del intérprete de mandatos cuando el script asociado no está realizando ninguna operación. Para poder hacer la medida, se lanzará un script en cada lenguaje que se “dormirá” durante 10 segundos, mientras que el script en Bash toma muestras del fichero ”/proc/<pid>/status” para obtener el consumo de memoria. El campo muestreado es “VmHWM” (Virtual Memory High Water Mark), indica el valor máximo de páginas residentes en memoria principal, expresado en KBs.
#Prueba1 (pseudocódigo)
imprimir("Nada (<Lenguage>)")
dormir(10)
imprimir("Finalizando - Nada (<Lenguage>)")
En estos resultados podemos destacar la enorme diferencia en consumo de memoria entre los diferentes lenguajes. Tcl y Lua obtienen resultados casi idénticos, de poco más de 500KB, a continuación se encuentra PHP con un consumo de casi el triple (1388KB) y por último Ruby con alrededor de 4 veces y media los primeros (2224KB). Se puede deducir sin ninguna duda que los intérpretes de Tcl y Lua son mucho más ligeros, y por lo tanto no sobrecargan el sistema.
Prueba 2: Latencia
Esta prueba mide el tiempo que tarda un script en ser invocado. Para realizar la medida, se ha recurrido a los scripts de la prueba anterior y a la herramienta time que calcula el tiempo que ha consumido un proceso en ejecutarse.
Los scripts han sido modificados para que en lugar que hacer una llamada que bloquee al proceso durante 10 segundos, se realicen 10 bloqueos de 1 segundo, con el objetivo de hacer más evidente las diferencias entre los lenguajes.
El resultado ideal sería que el tiempo de ejecución del proceso fuera de 10 segundos (el tiempo que permanece dormido), lo que se quiere estudiar en esta prueba es la diferencia entre el tiempo consumido y el tiempo de ejecución ideal de 10 segundos.
#Prueba2 (pseudocódigo)
imprimir("Nada (<Lenguage>)")
dormir(1)
dormir(1)
dormir(1)
dormir(1)
dormir(1)
dormir(1)
dormir(1)
dormir(1)
dormir(1)
dormir(1)
imprimir("Finalizando - Nada (<Lenguage>)")
Se puede observar en los resultados que Tcl, Lua y PHP se comportan de forma similar en esta prueba, habiendo muy poca diferencia entre ellos. Sin embargo, Ruby obtiene resultados peores que el resto, confirmando, junto con la primera prueba, que es un lenguaje que introduce más sobrecarga en el sistema que los demás.
Prueba 3: Velocidad de procesamiento
Esta prueba mide el tiempo que tarda cada lenguaje en realizar un bucle de 10^6 iteraciones, asignando en cada iteración el valor '1' a una variable residente en memoria.
#Prueba3 (pseudocódigo)
imprimir("CPU (<Lenguage>)")
for i=1..1000
{
for j=1..1000
{
z=1;
}
}
imprimir("Finalizando - CPU (<Lenguage>)")
En estos resultados se aprecia claramente que Tcl tiene un rendimiento bastante peor que los demás lenguajes, destacando Lua como el mejor de todos por una diferencia significativa.
Parece lógico que la gráfica de “Tiempos en modo usuario” sea muy parecida a la de “Tiempos totales”, ya que todo el programa se ejecuta en modo usuario y no hay llamadas al sistema (salvo las dos llamadas de imprimir, pero no son relevantes en este caso). Sin embargo, no se esperaba que la gráfica de “Tiempos en modo sistema” también se asemejara a éstas (aunque en otra escala).
Se ha querido investigar este fenómeno volviendo a realizar la prueba con un ligero matiz: la primera mitad de las muestras se tomarán con programas que realizan 1.000.000 iteraciones (como hasta el momento), pero en la segunda mitad, se ha dividido este valor entre 2 para el Tcl (que ejecutará 500.000 iteraciones) y se ha multiplicado por 5 para el resto de lenguajes (por lo tanto ejecutarán 5.000.000). Evidentemente esta segunda versión de la prueba no se realiza en igualdad de condiciones, pero ahora no se quiere comparar los lenguajes, sino comprobar si el tiempo en modo sistema está relacionado con el tiempo total de procesamiento, aunque no se haga ninguna llamada al sistema.
Tal y como se puede ver en las gráficas, efectivamente está relacionado el tiempo en modo sistema con el tiempo total de procesamiento, por lo tanto se puede afirmar que el alto valor del tiempo en modo sistema del Tcl (gráfica “Prueba 3: Tiempos en modo sistema”) es simplemente una consecuencia de su bajo rendimiento, y no se trata de una situación anómala.
Ya al margen de esta comparativa, se ha querido profundizar en este fenómeno para entender por qué se le contabiliza tiempo de sistema a un proceso que en principio sólo debería consumir tiempo en modo usuario. Para poder saber cómo se contabiliza el tiempo, se ha estudiado el código del sistema operativo del router (Linux v2.6.21), haciendo uso de la página web http://lxr.linux.no/#linux+v2.6.21 que permite visualizar el código y navegar a través de él de forma fácil.
Para empezar, los tiempos del proceso se almacenan en la estructura de datos del mismo (task_struct) en los campos utime y stime. Esta estructura de datos es la que se consulta para mostrar los tiempos del proceso, ahora se va a ver cómo se contabiliza el tiempo en ella.
Empezamos por la rutina de tratamiento de la interrupción de reloj, que es la que se encarga, entre otras cosas, de la contabilidad. Esta rutina depende de la arquitectura, y se encuentra en un fichero llamado time.c dentro de árbol de PowerPC (se puede acceder a través de este link: time.c:614. La rutina es la siguiente:
614 void timer_interrupt(struct pt_regs * regs) 615{ 616 struct pt_regs *old_regs; 617 int next_dec; 618 int cpu = smp_processor_id(); 619 unsigned long ticks; 620 u64 tb_next_jiffy; 621 622#ifdef CONFIG_PPC32 623 if (atomic_read(&ppc_n_lost_interrupts) != 0) 624 do_IRQ(regs); 625#endif 626 627 old_regs = set_irq_regs(regs); 628 irq_enter(); 629 630 profile_tick(CPU_PROFILING); 631 calculate_steal_time(); 632 633#ifdef CONFIG_PPC_ISERIES 634 if (firmware_has_feature(FW_FEATURE_ISERIES)) 635 get_lppaca()->int_dword.fields.decr_int = 0; 636#endif 637 638 while ((ticks = tb_ticks_since(per_cpu(last_jiffy, cpu))) 639 >= tb_ticks_per_jiffy) { 640 /* Update last_jiffy */ 641 per_cpu(last_jiffy, cpu) += tb_ticks_per_jiffy; 642 /* Handle RTCL overflow on 601 */ 643 if (__USE_RTC() && per_cpu(last_jiffy, cpu) >= 1000000000) 644 per_cpu(last_jiffy, cpu) -= 1000000000; 645 646 /* 647 * We cannot disable the decrementer, so in the period 648 * between this cpu's being marked offline in cpu_online_map 649 * and calling stop-self, it is taking timer interrupts. 650 * Avoid calling into the scheduler rebalancing code if this 651 * is the case. 652 */ 653 if (!cpu_is_offline(cpu)) 654 account_process_time(regs); 655 656 /* 657 * No need to check whether cpu is offline here; boot_cpuid 658 * should have been fixed up by now. 659 */ 660 if (cpu != boot_cpuid) 661 continue; 662 663 write_seqlock(&xtime_lock); 664 tb_next_jiffy = tb_last_jiffy + tb_ticks_per_jiffy; 665 if (per_cpu(last_jiffy, cpu) >= tb_next_jiffy) { 666 tb_last_jiffy = tb_next_jiffy; 667 do_timer(1); 668 timer_recalc_offset(tb_last_jiffy); 669 timer_check_rtc(); 670 } 671 write_sequnlock(&xtime_lock); 672 } 673 674 next_dec = tb_ticks_per_jiffy - ticks; 675 set_dec(next_dec); 676 677#ifdef CONFIG_PPC_ISERIES 678 if (firmware_has_feature(FW_FEATURE_ISERIES) && hvlpevent_is_pending()) 679 process_hvlpevents(); 680#endif 681 682#ifdef CONFIG_PPC64 683 /* collect purr register values often, for accurate calculations */ 684 if (firmware_has_feature(FW_FEATURE_SPLPAR)) { 685 struct cpu_usage *cu = &__get_cpu_var(cpu_usage_array); 686 cu->current_tb = mfspr(SPRN_PURR); 687 } 688#endif 689 690 irq_exit(); 691 set_irq_regs(old_regs); 692}
La llamada a la función que nos interesa es account_process_time en la línea 654. Navegando por la página descubrimos que esta llamada es una macro definida en el mismo fichero time.c:299
299#define account_process_time(regs) update_process_times(user_mode(regs))
La función update_process_times está definida en el fichero timer.c:1206, perteneciente al código del kernel independiente de la arquitectura.
1206 void update_process_times(int user_tick) 1207{ 1208 struct task_struct *p = current; 1209 int cpu = smp_processor_id(); 1210 1211 /* Note: this timer irq context must be accounted for as well. */ 1212 if (user_tick) 1213 account_user_time(p, jiffies_to_cputime(1)); 1214 else 1215 account_system_time(p, HARDIRQ_OFFSET, jiffies_to_cputime(1)); 1216 run_local_timers(); 1217 if (rcu_pending(cpu)) 1218 rcu_check_callbacks(cpu, user_tick); 1219 scheduler_tick(); 1220 run_posix_cpu_timers(p); 1221}
Es en este punto donde vemos, en las líneas 1213 y 1215, que se le suma 1 tick de reloj al proceso actual (como se ve en la línea 1208: a current). Si seguimos explorando el código, encontramos la funcion account_user_time en el fichero sched.c:3067 y account_system_time en sched.c:3088
3061/* 3062 * Account user cpu time to a process. 3063 * @p: the process that the cpu time gets accounted to 3064 * @hardirq_offset: the offset to subtract from hardirq_count() 3065 * @cputime: the cpu time spent in user space since the last update 3066 */ 3067 void account_user_time(struct task_struct *p, cputime_t cputime) 3068{ 3069 struct cpu_usage_stat *cpustat = &kstat_this_cpu.cpustat; 3070 cputime64_t tmp; 3071 3072 p->utime = cputime_add(p->utime, cputime); 3073 3074 /* Add user time to cpustat. */ 3075 tmp = cputime_to_cputime64(cputime); 3076 if (TASK_NICE(p) > 0) 3077 cpustat->nice = cputime64_add(cpustat->nice, tmp); 3078 else 3079 cpustat->user = cputime64_add(cpustat->user, tmp); 3080} 3081 3082/* 3083 * Account system cpu time to a process. 3084 * @p: the process that the cpu time gets accounted to 3085 * @hardirq_offset: the offset to subtract from hardirq_count() 3086 * @cputime: the cpu time spent in kernel space since the last update 3087 */ 3088 void account_system_time(struct task_struct *p, int hardirq_offset, 3089 cputime_t cputime) 3090{ 3091 struct cpu_usage_stat *cpustat = &kstat_this_cpu.cpustat; 3092 struct rq *rq = this_rq(); 3093 cputime64_t tmp; 3094 3095 p->stime = cputime_add(p->stime, cputime); 3096 3097 /* Add system time to cpustat. */ 3098 tmp = cputime_to_cputime64(cputime); 3099 if (hardirq_count() - hardirq_offset) 3100 cpustat->irq = cputime64_add(cpustat->irq, tmp); 3101 else if (softirq_count()) 3102 cpustat->softirq = cputime64_add(cpustat->softirq, tmp); 3103 else if (p != rq->idle) 3104 cpustat->system = cputime64_add(cpustat->system, tmp); 3105 else if (atomic_read(&rq->nr_iowait) > 0) 3106 cpustat->iowait = cputime64_add(cpustat->iowait, tmp); 3107 else 3108 cpustat->idle = cputime64_add(cpustat->idle, tmp); 3109 /* Account for system time used */ 3110 acct_update_integrals(p); 3111}
En estas funciones vemos que efectivamente se contabiliza el tiempo en los campos utime y stime. Por lo tanto, siempre se le contabiliza el tiempo al proceso que está actualmente en ejecución. Este comportamiento no genera ningún problema cuando el proceso está en modo usuario, pero sí puede dar lugar a situaciones “injustas” en modo sistema.
Supongamos el siguiente caso: El proceso A es un proceso que realiza continuamente operaciones de lectura/escritura en disco, y el proceso B es un proceso que sólo agota tiempo de usuario, sin hacer ninguna llamada al sistema (como es el caso de esta prueba). Supongamos que es el turno de ejecución del proceso A, quien pide al sistema operativo realizar una operación sobre disco, y por lo tanto queda bloqueado a la espera de una interrupción de disco para poder reanudar. En este momento, el proceso B entra en ejecución y consume tiempo en modo usuario; en un determinado momento se produce la interrupción de disco (por la que estaba esperando el proceso A) y mientras se está tratando (en modo sistema) se produce una interrupción de reloj (que suele ser la de mayor prioridad). En este escenario, se le contabilizaría un tick de reloj de tiempo de sistema a B, ya que el proceso en ejecución (current) es B; y por lo tanto se estaría sumando tiempo de sistema a un proceso que en principio sólo consumía tiempo de usuario.
Prueba 4: Manejo de tiras de caracteres
Esta prueba consiste en la concatenación de variables residentes en memoria, que almacenan tiras de caracteres, hasta llegar a los 5 millones de caracteres. Se tomarán varias muestras del consumo de memoria en cada uno de los lenguajes. El resultado de esta prueba será por una parte el pico con la mayor cantidad de memoria utilizada por el programa, y el tiempo de procesamiento.
#Prueba4 (pseudocódigo)
imprimir("Caracteres (<Lenguage>)")
MuestrearConsumoMemoria();
#Hacemos operaciones de concatenación de caracteres hasta los 5*10^6 caracteres
a="abcdefghij"
b="1234567890"
c=a+b+a+b+a+b+a+b+a+b+a+b+a+b+a+b+a+b+a+b; #200 caracteres
MuestrearConsumoMemoria()
d=c+c+c+c+c+c+c+c+c+c #2.000 caracteres
e=d+d+d+d+d+d+d+d+d+d #20.000 caracteres
f=e+e+e+e+e #100.000 caracteres
MuestrearConsumoMemoria()
g=f+f+f+f+f+f+f+f+f+f #1.000.000 caracteres
h=g+g+g+g+g #5.000.000 caracteres
MuestrearConsumoMemoria()
#Realizamos operaciones de mayúsculas y minúsculas
h=AMayusculas(h)
h=AMinusculas(h)
MuestrearConsumoMemoria()
imprimir("Caracteres - CPU (<Lenguage>)")
Nota: Se ha detectado una anomalía en LUA relacionada con el consumo de memoria. El recolector de memoria de LUA es menos agresivo que en los demás lenguajes y tarda más tiempo en entrar en ejecución, por tanto el consumo de memoria alcanza un nivel muy superior a los demás después de realizar las operaciones de conversión a mayúsculas y minúsculas, pudiendo llegar hasta los 38MB de memoria ocupada. Para solventarlo, se ha decidido ajustar algunos parámetros del recolector, para poder tener una medida comparable con los demás lenguajes.
Según el Manual de referencia de LUA, el recolector se controla mediante dos parámetros: setpause y setstepmul, se ha probado que el programa se comporta de forma similar a los demás lenguajes con el siguiente fragmento de código:
collectgarbage("setstepmul",325);

A la vista de los resultados, Ruby resulta ser el más eficiente en cuanto a tiempo de procesamiento y el mejor, junto con PHP, en consumo de memoria, a pesar de ser un lenguaje bastante “pesado”, tal y como se ha visto en las dos primeras pruebas. Por otro lado, PHP ofrece resultados peores que Ruby en cuanto a tiempos, pero bastantes buenos en comparación con Lua y Tcl.
Prueba 5: Manejo de ficheros
Esta prueba está diseñada para evaluar el tiempo de acceso a ficheros en cada uno de los lenguajes.
Se realizará una copia del fichero Origen.txt de 1millon de caracteres al fichero Destino.txt. La copia se hace byte a byte, leyendo de un fichero y escribiendo inmediatamente en el otro.
#Prueba 5 (pseudocódigo)
origen=open(argv[1]);
destino=open(argv[2]);
while (!eof(origen))
{
c=read(origen,1);
write(destino,c);
}
close(origen);
close(destino);
En esta prueba se ha detectado una situación inesperada: los lenguajes se comportan de forma diferente entre sí a la hora de leer/escribir. De esta forma, según el lenguaje, al solicitar una operación de lectura o escritura de 1 byte, internamente se traduce a una llamada al sistema con un tamaño mucho mayor, 1 o 2 bloques, por lo que el rendimiento es mucho mejor del esperado en algunos lenguajes y la comparativa no se realiza en igualdad de condiciones. Aún así, se considera válida la prueba ya que se realizan exactamente las mismas operaciones en los scripts de cada lenguaje.
A continuación se muestra una tabla resumen del número de bytes reales sobre lecturas/escrituras en disco cuando se solicita leer/escribir 1 byte respectivamente:
| Intérprete/Lenguaje | Tamaño lecturas (bytes) | Tamaño escrituras (bytes) |
|---|---|---|
| Tcl | 4096 | 4096 |
| Lua | 1 | 1/4096(*) |
| PHP | 8192 | 1 |
| Ruby | 1 | 8192 |
(*) Lua alterna escrituras de 1 byte con otras de 4096 bytes.
Prueba 6: Manejo de Sockets
Esta prueba consiste en enviar datos a través a un socket a un servidor de eco hecho en LUA ('Servidor.lua').
Los programas harán de clientes que se conectarán al servidor 1000 veces, en cada conexión abren el canal (socket), envían una línea con 62 caracteres, y cierran el canal.
#Prueba6 (pseudocódigo) (Servidor)
s = socket.bind("localhost", 9999)
print("Servidor conectado, esperando clientes en " + s.ip + ":" + s.puerto + "...")
while 1 do
client = s.accept()
linea = client.receive()
client.send(linea + "\n")
client.close()
end
#Prueba6 (pseudocódigo) (Cliente)
l="1234567890abcdefghijklmnopqrstuvwyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n"
for i = 1..1000
{
s=socket.open("localhost",9999);
s.send(l);
close(s);
}
En esta última prueba, se puede apreciar que PHP es el lenguaje que maneja los sockets de forma más eficiente, lo cual parece lógico al tratarse de un lenguaje orientado al desarrollo web. Lua ofrece unos resultados parecidos a PHP, a continuación se sitúa Ruby y por último, Tcl, con el peor rendiemiento.
Conclusiones
Para poder elegir el mejor lenguaje, se ha realizado una tabla resumen de todas las pruebas, teniendo en cuenta los resultados obtenidos. De forma que el lenguaje que obtiene mejor rendimiento en una prueba obtiene una puntuación de 1 y el peor un 4, por último se sumarán las puntuaciones y se elegirá el de menor puntuación:
| Prueba\Lenguaje | Tcl | Lua | PHP | Ruby |
|---|---|---|---|---|
| Prueba 1 | 1 | 1 | 2 | 3 |
| Prueba 2 | 1 | 1 | 1 | 2 |
| Prueba 3 | 3 | 1 | 2 | 2 |
| Prueba 4 | 4 | 3 | 2 | 1 |
| Prueba 5 | 3 | 1 | 3 | 2 |
| Prueba 6 | 4 | 2 | 1 | 3 |
| Total | 16 | 9 | 11 | 13 |
Conclusiones
Se ha decidido continuar el desarrollo de este proyecto con el lenguaje de scripting Lua, ya que cumple con todos los criterios de selección y es el lenguaje con mejor rendimiento según se ha comprobado anteriormente.



