<?xml version="1.0" encoding="utf-8"?>
<chapter>
    <title>Guía rápida para optimizar programas de GNOME</title>
    
    <para>Esta es una breve introducción a la optimización, de los cómos tanto de los porqués. Se dejan para posteriores artículos los detalles de herramientas y técnicas individuales pero se proporciona una colección de sugerencias y trucos.</para>
    
    <sect1 id="optimization-intro-TBL-what-are-we-optimizing">
        <title>¿Qué se está optimizando?</title>
        <para>Al optimizar GNOME lo primero que hay que recordar es esto: no se está intentando mejorar el programa, se está intentando hacer que las personas usen el equipo más felices.</para>
        <para>Programas mejores hacen a la gente más feliz pero existen algunas mejoras que los harán más felices que otras: tiempo de respuesta, tiempo de inicio, facilidad para acceder a comandos y que el equipo no tenga que usar la memoria de intercambio cuando más de dos programas estén abiertos.</para>
        <para>La optimización tradicional contempla conceptos como el uso de CPI, el tamaño del código, el número de pulsaciones del ratón y el uso de memoria de programa. Se ha elegido esta segunda lista para correlar con la primera, no obstante existe una diferencia importante. A la persona que usa GNOME no le importa la segunda lista, pero sí la primera. Al optimizar los programas de GNOME se reducirá el uso de CPU, el uso de memoria y todo aquello, pero son conceptos para el fin, no el objetivo final. Se optimiza para las personas.</para>
    </sect1>
    
    <sect1 id="optimization-intro-TBL-doing-optimization">
        <title>Realizar la optimización</title>
        <para>En la sección anterior se omitió un calificador importante: optimizar algo debe ser medible. No se puede medir la felicidad. No obstante se puede medir el tiempo de inicio de un programa para saber si se ha mejorado. La felicidad del usuario aumentará pues.</para>
        <para>La optimización es el proceso de medida, refinamiento y remedida. Lo primero que debe hacer es encontrar una forma de medir lo que está optimizando. Idealmente la medida es un simple número, por ejemplo: el tiempo que se tarda en realizar una tarea. Esta es su prueba, es la única forma de saber si está ganando o perdiendo. Existe una gran diferencia entre un programa que <emphasis>debería</emphasis> ser rápido y un programa que <emphasis>es</emphasis> rápido.</para>
        <para>Una vez que tiene una prueba de rendimiento básica debe encontrar por qué su código no lo está haciendo tan bien como debería. Es tentador hacerlo inspeccionando: simplemente mirar el código y tratar de encontrar algo que parece que necesita una mejora. Estará perdiendo el tiempo, Usar un perfilador para obtener una lista detallada de lo que su programa está haciendo es, realmente, la única forma de estar seguro.</para>
        <para>Generalmente el problema está aislado en pequeñas partes del código. Elija la peor parte y concéntrese en ella primero. Una vez que lo haya hecho, vuelva al perfilador y repita el proceso. Según proceda, las mejoras obtenidas en cada paso se harán cada vez más pequeñas, en algún punto tendrá que decidir que los resultados son suficientes. Si sus esfuerzos sólo están obteniendo un 10% de mejoras entonces hace tiempo que pasó el punto en el que debería haber parado.</para>
        <para>No se olvide del objetivo final. Por ejemplo, en lugar de intentar mejorar un trozo de código, pregúntese si realmente se necesita ejecutar ese código. ¿Se podría combinar con otro trozo de código? ¿Se pueden guardar y reusar cálculos previos? No necesitará optimizarse si está en un lugar donde el usuario no lo va a notar. O aún peor, el código puede ya estar optimizado y está haciendo cálculos pesados ahora para evitar hacerlos luego. El código no se ejecuta aisladamente ni tampoco lo hace el proceso de optimización.</para>
    </sect1>
    <sect1 id="optimization-intro-TBL-hints">
        <title>Sugerencias</title>
        <itemizedlist>
            <title>Lo fundamental</title>
            <listitem>
                <para>Vuelva a ejecutar su prueba de rendimiento después de cada cambio que realice sobre el código y mantenga un registro de todo lo que cambia y de cómo afecta al rendimiento. Esto le permite deshacer errores y también le ayuda a no repetirlos.</para>
            </listitem>
            <listitem>
                <para>Asegúrese de que su código es correcto y está libre de errores antes de optimizarlo. Compruebe que permanece correcto y libre de errores después de haberlo optimizado.</para>
            </listitem>
            <listitem>
                <para>Optimizar al nivel más alto antes de optimizar los detalles.</para>
            </listitem>
            <listitem>
                <para>Usar el algoritmo correcto. El clásico ejemplo de libro de texto es usar ordenación rápida en lugar de ordenación de burbuja. Existen muchos otros, algunos ahorran memoria, algunos ahorran CPU. También debe ver qué atajos de teclado puede crear: puede hacerlo más rápido que una ordenación rápida si está preparado para tomar ciertos compromisos.</para>
            </listitem>
            <listitem>
                <para>La optimización es intercambio. Obtener resultados mejora los cálculos pero aumenta la memoria en uso. Guardar datos al disco ahorra memoria pero cuesta tiempo al cargarlos de nuevo desde el disco.</para>
            </listitem>
            <listitem>
                <para>Asegúrese de elegir cierta variedad de entradas que optimizar. Si no lo hace es fácil que termine con un trozo de código cuidadosamente optimizado para un campo y no para otros.</para>
            </listitem>
            <listitem>
                <para>Evite las operaciones caras: múltiples lecturas de disco pequeñas. Use un montón de memoria para que el área de intercambio («swap») se haga innecesario. Evite cualquier cosa que escriba o lea innecesariamente del disco. La red también es lenta. Evite también operaciones gráficas que necesitan una respuesta del servidor X.</para>
            </listitem>
        </itemizedlist>
        <itemizedlist>
            <title>Trampas para los imprudentes</title>
            <listitem>
                <para>Esté atento a efectos laterales. Generalmente son interacciones extrañas entre diferentes secciones del código, una extensión de una parte puede retardar otra.</para>
            </listitem>
            <listitem>
                <para>Al cronometrar el tiempo del código, incluso en un sistema silencioso, los eventos fuera del programa añaden ruido a los resultados del tiempo. Haga la media sobre múltiples ejecuciones. Si el código es muy pequeño la resolución del tiempo también es un problema. En este caso mida el tiempo que el equipo tarda en ejecutar el código 100 o 1000 veces. Si los tiempos que está obteniendo son algo superiores a unos pocos segundos todo debería estar correcto.</para>
            </listitem>
            <listitem>
                <para>Es muy fácil perderse con el perfilador. Existen historias de programadores optimizando el bucle de inactividad porque es donde se perdía todo su tiempo. No optimice código del que el usuario no se preocupe.</para>
            </listitem>
            <listitem>
                <para>Recuerde los resultados del servidor X. El uso de memoria de su programa no incluye los pixmaps almacenados en los procesos del servidor X, pero aún siguen usando memoria. Use xrestop para ver los recursos que está usando su programa.</para>
            </listitem>
        </itemizedlist>
        <itemizedlist>
            <title>Consejos de bajo nivel</title>
            <listitem>
                <para>Al optimizar el uso de memoria considere la diferencia entre el uso de pico y el uso medio de memoria. Alguna memoria siempre está reservada, esto generalmente es malo. Alguna está temporalmente reservada, esto puede ser aceptable. Herramientas como massif usan el concepto de espacio-tiempo, el producto de la memoria usada y el tiempo durante el que estuvo reservada.</para>
            </listitem>
            <listitem>
                <para>Controle el tiempo de trozos de código simplificados que sólo hacen cosas esenciales, esto proporciona un límite absoluto inferior en el tiempo que usará el código. Por ejemplo, al optimizar un bucle, controle el tiempo del bucle vacío. Si aún es mucho tiempo cualquier intento de optimización no ayudará y deberá cambiar su diseño. Asegúrese de que el compilador no desoptimiza el bucle vacío.</para>
            </listitem>
            <listitem>
                <para>Mueva el código fuera de los bucles internos. Un trozo de código más complicado que se ejecuta una sola vez es mucho más rápido que un trozo de código más simple que se ejecuta mil veces. Evite llamar habitualmente a código lento.</para>
            </listitem>
            <listitem>
                <para>Proporcione al compilador tantos sugerencias como le sea posible. Use la palabra clave «const». Use <envar>G_INLINE_FUNC</envar> para funciones cortas frecuentemente llamadas. Busque <envar>G_GNUC_PURE</envar>, <envar>G_LIKELY</envar> y otras macros misceláneas de glib. Use las macros en lugar de palabras clave específicas de gcc para asegurar la portabilidad.</para>
            </listitem>
            <listitem>
                <para>No use lenguajes de ensamblado. El código no es portable y puede ser más rápido en un procesador que en otro, además no está garantizado que sea rápido en cada procesador que soporta esa arquitectura (ej. Athlon contra Pentium 4).</para>
            </listitem>
            <listitem>
                <para>No reescriba una rutina existente de una biblioteca a no ser que esté seguro de que es demasiado lenta. Muchas rutinas de bibliotecas de uso intensivo por la CPU ya se han optimizado. Algunas rutinas de bibliotecas son lentas, especialmente aquellas que realizan llamadas al sistema operativo.</para>
            </listitem>
            <listitem>
                <para>Minimice el número de bibliotecas a las que enlaza. Cuanto menor sea el número de bibliotecas que enlazar, más rápido se iniciará el programa. Es una tarea difícil en GNOME.</para>
            </listitem>
        </itemizedlist>
        <itemizedlist>
            <title>Trucos de alto nivel</title>
            <listitem>
                <para>La ventaja de la concurrencia. Esto no significa simplemente usar varios procesadores, también significa aprovecharse del tiempo que el usuario pasa pensando en qué harán después, para realizar algunos cálculos anticipados. Haga cálculos mientras se espera la carga de datos desde el disco. Aprovéchese de los recursos múltiples, úselos todos a la vez.</para>
            </listitem>
            <listitem>
                <para>Engañe. El usuario sólo tiene que pensar que el equipo es rápido, no importa si realmente lo es o no. Es el tiempo entre el comando y la respuesta lo que importa, no importa si la respuesta estaba precalculada, cacheada o se trabajará en ella en cualquier momento posterior conveniente, siempre que el usuario obtenga lo que espera.</para>
            </listitem>
            <listitem>
                <para>Realice trabajo en el lazo de inactividad. Es más fácil de programar que usar programación completa para multihilo pero aún así las cosas se hacen fuera de la vista del usuario. No obstante, sea cuidadoso, si se queda demasiado tiempo en el lazo de inactividad su programa acabará siendo lento. Por eso debe devolver regularmente el control al lazo principal.</para>
            </listitem>
            <listitem>
                <para>Si todo lo demás falla, dígale al usuario que el código va a ser lento y ponga una barra de progreso. No estarán tan contentos como si hubiese mostrado el resultado pero al menos sabrán que el programa no se ha colgado y que se pueden ir a preparar un café.</para>
            </listitem>
        </itemizedlist>
    </sect1>
</chapter>
