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
AngelScriptAngelScript 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>)")

Prueba 1 (Consumo de memoria)


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>)")

Prueba 2 (Tiempos totales)
Prueba 2 (Tiempos en modo usuario) Prueba 2 (Tiempos en modo sistema)






















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>)")

Prueba 3 (Tiempos totales)
Prueba 3 (Tiempos en modo usuario) Prueba 3 (Tiempos en modo sistema)
























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.

Prueba 3 (V2) (Tiempos totales)
Prueba (V2) 3 (Tiempos en modo usuario) Prueba 3 (V2) (Tiempos en modo 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);

Prueba 4 (Tiempos totales)
Prueba 4 (Tiempos en modo usuario)Prueba 4 (Tiempos en modo sistema)





















Prueba 4 (Consumo de memoria)


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);

Prueba 5 (Tiempos totales)
Prueba 5 (Tiempos en modo usuario) Prueba 5 (Tiempos en modo sistema)






















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);
}

Prueba 6 (Tiempos totales)
Prueba 6 (Tiempos en modo usuario) Prueba 6 (Tiempos en modo sistema)























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\LenguajeTclLuaPHPRuby
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.

 
proyectos/teldatsi/eleccion_del_lenguaje.txt · Última modificación: 2012/10/08 17:58 (editor externo)
 
Recent changes RSS feed Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki