Consejos de rendimiento de Euphoria
En cualquier lenguaje de programación, y especialmente en Euphoria, tiene que hacer realmente mediciones antes de sacar conclusiones acerca del rendimiento. Euphoria provee tanto análisis por conteo de ejecución, como por tiempo (solo DOS32). Lea refman.doc. Frecuentemente se sorprenderá con los resultados de estos perfiles. Concentre sus esfuerzos en los lugares del programa que usan un alto porcentaje del tiempo total (o al menos, los que se ejecutan una gran cantidad de veces). No tiene sentido reescribir una sección del código que usa el 0.01% del tiempo total. Comúnmente habrá un lugar, o unos pocos donde este código haga una diferencia significativa. También puede medir la velocidad del código usando la función time(), por ejemplo: atom t t = time() for i = 1 to 10000 do -- pequeña porción de código end for ? time() - t Tendría que reescribir la pequeña porción de código de formas diferentes, para determinar cual es la forma más rápida.
El análisis de perfiles de ejecución le mostrará los puntos calientes de su programa. Normalmente están dentro de los ciclos. Observe cada cálculo dentro de un ciclo y pregúntese si realmente es necesario que ocurran cada vez que el ciclo se ejecuta, o si podría hacerse una vez y fuera del ciclo.
La suma es más rápida que la multiplicación. A veces puede reemplazar una multiplicación por la variable de ciclo con una suma. Algo como: for i = 0 to 199 do poke(screen_memory+i*320, 0) end forse convierte en: x = screen_memory for i = 0 to 199 do poke(x, 0) x = x + 320 end for Guardando resultados en variables
Si tiene una rutina pequeña y rápida, pero se la llama una enorme cantidad de veces, ahorrá tiempo haciendo la operación en-línea, en lugar de llamar la rutuina. Su código puede hacerse menos legible, por lo que sería mejor usar la operación en-línea, solamente en los lugares que generan un montón de llamadas a la rutina.
Euphoria le permite operar en una secuencia grande de datos usando una sentencia única. Esto le ahorra de escribir un ciclo donde se procesa un elemento por vez. Por ejemplo: x = {1,3,5,7,9} y = {2,4,6,8,10} z = x + ycontra: z = repeat(0, 5) -- si es necesario for i = 1 to 5 do z[i] = x[i] + y[i] end for En la mayoría de los lenguajes interpretados, es mucho más rápido procesar una secuencia (array) completa en una sentencia, que hacer operaciones escalares dentro de un ciclo. Esto se debe a que el intérprete tiene una gran cantidad de consumo de recursos para cada sentencia que ejecuta. Euphoria es diferente. Euphoria es muy liviano, con poco consumo de recursos interpretativos, asique las operaciones sobre secuencias no siempre ganan. La única solución es medir el tiempo en ambas formas. El costo por elemento es comúnmente más bajo cuando se procesa una secuencia dentro de una sentencia, pero hay consumos de recursos asociados con la asignación de desasignación de secuencias que puede alterar la escala de otro modo.
Euphoria automáticamente optimiza ciertos casos especiales. Las 'x' e 'y' de más abajo, podrían ser variables o expresiones arbitrarias. x + 1 -- más rápido que x + y general 1 + x -- más rápido que y + x general x * 2 -- más rápido que x * y general 2 * x -- más rápido que y * x general x / 2 -- más rápido que x / y general floor(x/y) -- donde x e y son enteros, es más rápido que x/y floor(x/2) -- más rápido que floor(x/y) La 'x' de abajo es una variable simple, y la 'y' cualquier variable o expresión: x = append(x, y) -- más rápido que z = append(x, y) general x = prepend(x, y) -- más rápido que z = prepend(x, y) general x = x & y -- donde x es mucho más grande que y, -- es más rápido que z = x & y general Cuando usted escribe un ciclo que "hace crecer" una secuencia, agregando o concatenando los datos en ella, el tiempo, en general, crecerá en proporción con el cuadrado del número (N) de elementos que está agregando. Sin embargo, si usted puede utilizar una de las formas optimizadas especiales de append(), prepend() o de concatenación enumeradas arriba, el tiempo crecerá solo en proporción con N (aproximadamente). Esto podía ahorrarle una cantidad de tiempo enorme al crear una secuencia extremadamente larga (podría también utilizar repeat() para establecer el tamaño máximo de la secuencia, y después completar los elementos en un ciclo, según se discute más abajo). Para obtener mayor velocidad, convierta: lado-izquierdo = lado-derecho op expresióna: lado-izquierdo op= expresión toda vez que el lado-izquierdo contenga al menos 2
índices, o por lo menos, un índice y un subrango. En los
casos más simples, las dos formas ejecutan a la misma velocidad
(o a una velocidad muy próxima).
Escribir texto en la pantalla usando puts() o printf() es bastante lento. Si es necesario, en DOS32, puede hacerlo mucho más rápido escribiendo en la memoria de video, o usando display_text_image(). Hay un consumo muy grande de recursos en cada puts() a la pantalla, y un costo relativamente pequeño por caracter. El consumo de recursos con exw (WIN32) es especialmente alto (en Windows 95/98/ME, al menos). Linux y FreeBSD están en medio de DOS32 y WIN32 en relación a la velocidad de salida. Por lo tanto, tiene sentido armar una cadena grande antes de llamar a puts(), en lugar de llamarla para cada caracter. Sin embargo, no hay ventaja en armar una cadena grande que una línea. La lentitud de la salida de texto se debe principalmente al consumo de recursos del sistema operativo.
Algunas rutinas comunes son estremadamente rápidas. Probablemente no podría hacer el trabajo más rápido de ninguna otra forma, aún si usa lenguaje C o ensamblador. Algunas de ellas son:
Otras rutinas son razonablemente rápidas, pero en algunos casos en que la velocidad es crucial, tendría que ser capaz de hacer ese trabajo más rápido. x = repeat(0,100) for i = 1 to 100 do x[i] = i end fores algo más rápido que: x = {} for i = 1 to 100 do x = append(x, i) end for porque append()
tiene que asignar y reasignar el espacio mientras que 'x' crece de tamaño.
Con repeat(), el espacio
para 'x' se asigna una vez al comienzo. (append()
es suficientemente inteligente para no asignar espacio con cada
append() a 'x'. Asignará
algo más de lo que necesita, para reducir la cantidad de reasignaciones).
Puede reemplazar: remainder(x, p)con: and_bits(x, p-1) para mayor velocidad cuando 'p' es una potencia positiva
de 2. 'x' tiene que se un entero no negativo que quepa en 32 bits.
arctan() es más rápida que arccos() o arcsin().
La función find() de Euphoria es la forma más rápida de buscar un valor en una secuencia de hasta 50 elementos. Más allá de esto, debería considerar el uso de una tabla hash (demo\hash.ex) o de un árbol binario (demo\tree.ex).
En la mayoría de los casos puede usar la rutina shell sort de include\sort.e. Si tiene una enorme cantidad de datos para ordenar, debería probar uno de los ordenamientos de demo\allsorts.e (por ejemplo, great sort). Si los datos son demasiado grandes para entrar en memoria, no se fie de la capacidad de intercambio de memoria de Euphoria. En su lugar, ordene unos pocos de miles de registros por vez y escríbalos en una serie de archivos temporales. Luego, una todos los archivos temporales en otro ordenado completamente. Si sus datos son enteros solamente, y están dentro de un rango razonablemente acotado, pruebe el bucket sort de demo\allsorts.e.
A medida que las CPU incrementan su velocidad, la brecha entre la velocidad de la memoria cache del chip y la velocidad de la memoria principal o DRAM (memoria dinámica de acceso aleatorio) se hace cada vez mayor. Podría tener 256 Mb de DRAM en su computadora, pero el cache del chip es probable que sea solo de 8K (datos) más 8K (instrucciones) on a Pentium, o 16K (datos) más 16K (instrucciones) en un Pentium con MMX o un Pentium II/III. La mayoría de las máquinas tienen también un cache de "nivel 2" de 256K o 512K. Un algoritmo que recorre de una secuencia larga de varios miles de elementos o más, muchas veces, realizar desde el comienzo hasta el final una operación pequeña en cada elemento, no hará buen uso del cache de datos del chip. Puede ser que sea mejor recorrerla una vez, aplicando varias operaciones a cada elemento, antes de moverse al elemento siguiente. El mismo argumento se mantiene cuando su programa comienza a intercambiar, y los datos menos recientemente usados se mueven al disco. Estos efectos del cache no se sienten tanto en Euphoria, ya que están en lenguajes compilados de bajo nivel, pero son mensurables.
Euphoria le permite llamar rutinas escritas en código de máquina Intel de 32 bits. En WIN32, Linux y FreeBSD puede llamar rutinas de C en archivos .dll o .so, y esas rutinas de C pueden llamar a sus rutinas Euphoria. Podría necesitar llamar rutinas de C o en código de máquina porque algo no se puede hacer directamente en Euphoria, o debería hacerlo para mejorar la velocidad. Para aumentar la velocidad, el código de máquina o las rutinas de C necesitan hacer una cantidad significativa de trabajo en cada llamada, de lo contrario el consumo de recursos de configurar los argumentos y hacer las llamadas, ocupará la mayor parte del tiempo, y puede ser que no representen ninguna ganacia. Muchos programas tienen alguna operación interna que consume la mayor parte del tiempo de CPU. Si puede codificar esto en C o código de máquina mientras deja el grueso del programa en Euphoria, puede ser que alcance una velocidad comparable a C, sin sacrificar la flexibilidad y seguridad de Euphoria.
Puede descargar el Traductor Euphoria a C desde el sitio web de RDS. Este traducirá cualquier programa Euphoria en un conjunto de archivos fuente de C que luego podrá compilar usando un compilador de C. El archivo ejecutable que obtiene al usar el Traductor debería correr igual, pero más rápido que cuando usa el intérprete. El incremento de velocidad puede ir desde un procentaje muy bajo hasta un factor de 5 o más. |