El PHP es un buen lenguaje pero es un lenguaje interpretado y para ciertas tareas puede compensar utilizar otro lenguaje de más alto nivel como el C/C++ o incluso programar rutinas en ensamblador.
Dado que la mayoría de las distribuciones con las que trabajamos son UBUNTU podemos instalar el ensamblador de forma sencilla para crear objetos .o que pueden generar su propio ejecutable o usarse en otras rutinas de C o incluso en PHP mediante el comando “apt-get install nasm“.
Para conocer como programar en ensamblador en Linux por supuesto hay que tener conocimientos sobre lenguaje maquina (assembler) y sobre todo de arquitectura sobre todo en x86 y especialmente sobre las llamadas al núcleo(kernel) del sistema de LINUX mediante syscalls INT 0x80. Más info sobre llamadas al sistema aquí.
Y es que aunque parece un lenguaje que ya no se emplea en ciertos casos podemos acelerar los procesos de nuestro proyecto gracias a programar a un nivel tan bajo. Y es que se puede mediante el NASM generar código objeto ELF tipo .o y enlazar la programación en cualquier lenguaje o incluso crear subrutinas.
En AGENCIA LA NAVE sabemos programar en lenguaje maquina con lo que podemos llegar mucho más lejos que la competencia. En integrar código eficiente esta la diferencia.
A modo de ejemplo vamos a comparar este código en PHP:
<?php for($i=4294967295;$i<>0; $i--){ // no hacer nada } echo "Hola que tal"; ?>
Con esté código programado en ASM.
section .text ; parte definida del código del programa global _start ; Se necesita poner para que se pueda usar LD _start: ; Le dice al LD el punto de entrada para linkar xor edx,edx ; un XOR exclusivo siempre da 0, 1xor1=0 => edx=0 dec edx ; es un registro de 32 bits => edx=0xFFFFFFFF = máx valor _bucle: ; definimos el punto del bucle dec edx ; decrementa EDX => edx=edx-1 afecta a los FLAGS de la CPUjne _bucle ; Jump if Not Equal to CERO => salta si no es EDX CERO mov edx, len ;tamaño del mensaje definido area datos abajo mov ecx, msg ;mensaje a escribir con la llamada al sistema mov ebx, 1 ;file descriptor (stdout) (elegimos la salida) mov eax, 4 ;system call number (sys_write)(escribir en descriptor) int 0x80 ;llamamos al kernel de linux mov eax, 1 ;system call number (sys_exit) => solicitamos terminar int 0x80 ;llamamos al kernel de linux con la función EAX definida section .data ; esta es la parte de datos msg db 'Que tal!',0xa ; una cadena que finaliza con un CR len equ $ - msg ; tamaño de la cadena= POS-posiniciomensaje
Para general el código una vez ya tenemos el NASM instalado en nuestro ubuntu debemos hacer los siguientes pasos.
nasm -f elf sample.asm 2>&1 ld -m elf_i386 -s -o demo *.o 2>&1
Ahora probamos ambas programaciones para comprobar lo que tarda una y lo que tarda la otra: Ambas deben contar un número desde el máximo posible de 32 bits y al final ir restando hasta CERO y luego ya poner la cadena.
/home/test# ./demo Que tal! /home/test# time ./demo Que tal! real 0m4.360s user 0m4.356s sys 0m0.000s /home/test# time php sample.php real 9m36.492s user 9m36.484s sys 0m0.028s [1]+ Hecho time php sample.php > tardon
La programación en ASM tarda unos 4,5 segundos mientras que la misma tarea realizada en PHP tarda en ejecutarse 9 minutos y 36 segundos. ¿Seguro que no es útil utilizar ensamblador para ciertas tareas?
El código en lenguaje maquina se puede de hecho integrar en el momento que lo necesitamos en ASM como se presenta en el siguiente ejemplo:
<?php // cualquier tarea system("/home/test/demo"); // llamada a un modulo en ASM(=>OPCODE) ?> /home/test# time php sample2.php Que tal! real 0m4.376s user 0m4.348s sys 0m0.024s
Podemos ver que apenas retrasa integrar en PHP mediante un system ejecutar el código en lenguaje maquina de la parte que nos interese por ser ejecutada en dicho lenguaje. Puede interesar ejecutar ciertas partes de la programación en C que al ser más cercano a la maquina y crear ficheros objeto también aumenta la velocidad bastante con sistemas de optimización cada vez mejores.
Aún así conocer como funcionan los registros, la redirección de memoria, los flags y los comandos opcodes maquina puede ayudarnos a crear rutinas puntuales que optimicen ciertas partes del código de nuestra programación.
section .data ;Segmento de datos userMsg db 'Introduzca un numero: ' ; Cadena de solicitud de numero lenUserMsg equ $-userMsg ; Tamaño de la cadena dispMsg db 'Ha introducido : ' ; La cadena introducida lenDispMsg equ $-dispMsg ; tamaño de la nueva cadena section .bss ;segmento con datos que no se inicializan: Solo reserva memoria num resb 5 ; reservamos 5 bytes para guardar los 5 caracteres que teclee el usuario section .text ;segmento de código global _start _start: ;usamos la función syscall write (4) para escribir una cadena mov eax, 4 ; syscall write mov ebx, 1 ; destino => SALIDA stdout mov ecx, userMsg ; indicamos posición de memoria cadena mov edx, lenUserMsg ; indicamos su tamaño int 80h ; llamamos al núcleo de linux para hacer el print /echo ;Leemos aquí una cadena mediante la llamada READ y guardamos 5 caracteres mov eax, 3 ; syscall read mov ebx, 2 ; de donde? stdin (2) entrada mov ecx, num ; memoria dónde guardar el numero (5 bytes) mov edx, 5 ;5 bytes (numeric, 1 for sign) max 5 cars. int 80h ; llamamos al núcleo de linux para hacer el READ de la entrada ;Presentamos con la misma llamada que arriba lo tecleado por el cliente ; cadena programada mov eax, 4 ; syscall write mov ebx, 1 ; destino stdout mov ecx, dispMsg ; mensaje presentando mov edx, lenDispMsg ; donde esta la cadena int 80h ;Presentamos los 5 caracteres mov eax, 4 ; syscall WRITE mov ebx, 1 ; stdout mov ecx, num ; memoria dónde esta la parte leída mov edx, 5 ; 5 caracteres int 80h ; int 0x80 ; Hacemos EXIT a nivel de núcleo mov eax, 1 ; syscall exit mov ebx, 0 ; sin devolver error int 80h ; solicitamos finalizar
Podemos ver el resultado el ejecutable ocupa menos de 500 bytes. Si lo comparamos con un código en C que ocupa más de 750KB y eso que lo hemos compilado de forma estática. Incluso de forma dinámica ocupa más de 7500 bytes frente a los menos de 500 bytes en ASM. (Y hace la misma tarea).
#include <stdio.h> int main (int argc,char **argv){ char cad[5]; printf("Introduzca un numero:\n"); fgets(cad, 5, stdin); printf("La cadena introducida es: %s\n",cad); return 0; } #Compilando de forma estática: #sample2 es el código anterior enlazado en assembler) /home/test# cc -o sample2c -static ejemplo.c /home/test# ls -l sample2 -rwxr-xr-x 1 root root 496 may 13 10:15 sample2 /home/test# ls -l sample2c -rwxr-xr-x 1 root root 751603 may 13 10:25 sample2c Compilando ahora sin estatica (en versión dinamica) cc -o sample2c ejemplo.c /home/test# ls -l sample2c -rwxr-xr-x 1 root root 7317 may 13 10:27 sample2c
Lo mejor del lenguaje ensamblador es que controlamos al 100% lo que hacemos si bien el siguiente nivel de optimización pasaría por no llamar al núcleo. En cualquier caso el sistema operativo debe controlar muchas tareas y mediante las llamadas podremos hacerlo absolutamente todo. Es interesante utilizar ensamblador para crear funciones de encriptación de datos o tareas complejas que deban ser optimizadas.
Al crearse objetos se pueden definir funciones en ASM para su uso en C o C++ y así accelerar código de forma eficiente. Os ponemos un ejemplo sencillo para que veáis como se debe crear el fichero .asm y el .c y como fusionarlos.
sumar.asm ; el ELF32 se pasa por PILA los contenidos de las variables ; El resultado se devuelve en elf32 en EAX (seria RAX si fuera de 64 bits) ; ----------------------------------------------------------------------------- global sumartres section .text sumartres: mov eax,[esp+4] add eax,[esp+8] add eax,[esp+12] ret ; the max will be in eax Creamos el modulo: /home/test# nasm -f elf32 sumar.asm Ahora disponemos de sumar.o (con la funcion sumartres) Creamos ahora codigo llamadasumar.c Indicamos en el código que definimos los parametros en 32 bits #include #include uint32_t sumartres(uint32_t, uint32_t, uint32_t); int main() { printf("%d\n", sumartres(1, 2, 3)); printf("%d\n", sumartres(2, 3, 4)); return 0; } Y ahora compilamos el código: /home/test# cc -o llamadasumar llamadasumar.c sumar.o Si ejecutamos ahora el código podemos ver como funciona perfectamente. /home/test# ./llamadasumar 6 9
Cuando se emplean procesadores de 64 bits los parámetros de las funciones se pasan de forma diferente. Os recomiendo leer la guía de siguiente que os puede ayudar a entender porque se cambia de convención para pasar todos los parámetros posibles por los registros lo cual es más eficiente.
Esperamos que ahora os quedé más claro que conocer ASM no es una mala cosa y por supuesto puede ayudar a mejorar partes de una programación que no se ejecuta de forma óptima. Se puede mejorar el algoritmo pero también la forma en la se ejecuta en el procesador.
En AGENCIA LA NAVE trabajamos cada día para mejorar su web integrando las últimas tecnologías pero también usando herramientas que aunque llevan años en el sector pueden mejorar el rendimiento de su proyecto y lanzar lo al éxito.