En este artículo de LWN se cuenta una curiosa historia de microoptimizaciones que no sirven para nada. Resulta que en equipos modernos la diferencia de tiempo de acceso entre memoria y registro de procesador es enorme, el tiempo necesario para acceder a una dirección de memoria RAM no cacheada previamente es de cientos de ciclos de CPU.
Para ayudar a los programadores a sortear esta desventaja, las CPUs modernas tienen instrucciones de "prelectura" (prefetch). Se trata de instrucciones que por si solas no hacen nada, pero advierten al procesador de que se va a leer una dirección concreta de memoria en un futuro muy cercano, información que el procesador aprovecha para decir al controlador de memoria que vaya leyendo esa dirección de memoria y colocándola en el caché L1. Así, cuando se acabe leyendo de verdad, no habrá que esperar cientos de ciclos sin hacer nada. En el kernel Linux, se utilizan las instrucciones de "prefetch" en muchos sitios, por ejemplo al ir leyendo elementos de una lista enlazada se hace un "prefetch" del próximo elemento. Sin embargo, mediciones recientes han determinado que el efecto práctico de esta técnica es el enlentecimiento del kernel - concretamente, un 0.5% más lento en el tiempo empleado en compilar un kernel.
¿Qué ha pasado? Los procesadores modernos tienen una porción de la CPU dedicada al análisis heurístico del código que se ejecuta, con el propósito de adivinar qué rutas de código se van a tomar, que partes de la memoria se van a leer, optimizar el código y hacer "prefetchs" por su cuenta (pueden estar seguros que también intentan detectar si el programa en ejecución es algún famoso benchmark de CPUs). Resulta que el analizador de código de los procesadores Intel es capaz de adivinar los "prefetchs" necesarios con más eficiencia que los programadores del kernel y GCC - hasta el punto de que esas microoptimizaciones manuales le molestan y disminuyen el rendimiento. Resumiendo, que en la próxima versión se ha eliminado todo tipo de prefetching de Linux, y es más rápido.
Este tipo de cosas, por cierto, son las que han puesto su granito de arena para que x86 triunfe y cosas como Itanium fracasen. Itanium está diseñado para que el software y el compilador hagan casi todo el trabajo de optimización, de hecho es el compilador el que debe decidir cosas como si una instrucción depende de otra y si, por tanto, se pueden procesador ambas simultáneamente o si deben ser serializadas la una detrás de la otra. De ese modo, el complejo optimizador de código se hace teóricamente innecesario y el diseño de la CPU se simplifica. Pero en la práctica resultó que los compiladores no son (y probablemente nunca sean) tan buenos y exprimir la potencia de un Itanium requiere unos esfuerzos de optimización impresionantes. Mientras tanto, el académicamente horroroso y recargado analizador de código de los x86 es capaz de optimizar en tiempo de ejecución cualquier basura que le echen encima.
Buenas, ya que hablas de compiladores ¿Puedes dar alguna opinion sobre ekopath?
ResponderEliminarEkopath, el compilador que se ha puesto de moda porque sí!
ResponderEliminarAnónimo: No tengo ni idea de arquitectura interna de compiladores, pero los números que se ven por ahí parecen bastante buenos.
ResponderEliminar... eso se aplica en los equipos con procesador a partir del PentiumPro, supongo que si tienes un procesador antigüo o un Intel Atom no hará mucha gracia...
ResponderEliminarDiego, y que pasará con otras arquitecturas no x86? ARM por ej. no se verá afectada?
ResponderEliminarMagyar: Depende de cada procesador. Sospecho que en ARM se notará algo, ellos no se permiten el lujo de gastar montones de transistores en cosas así, en su caso puede tener sentido confiar más en el compilador.
ResponderEliminarExcelente apunte. Gracias por la información. Con la lectura se me ha aclarado el concepto 'prefetch'.
ResponderEliminar