Hay dos áreas para optimizar posiblemente la velocidad:
- Donde se pasa la mayor parte del tiempo
- El código que más se llama
¿Cuál es el mejor lugar para comenzar a optimizar?
A menudo, el código que se llama con mayor frecuencia ya tiene bajos tiempos de ejecución. ¿Optimiza las áreas más lentas y menos llamadas o pasa tiempo optimizando las áreas más rápidas y muy utilizadas?
optimization
profiling
Michael K
fuente
fuente
Respuestas:
Debe ignorar las pequeñas eficiencias el 95% del tiempo. Primero, haz que funcione correctamente , luego analiza ...
Tu diseño.
Su elección de algoritmos de alto nivel puede tener un gran impacto en el rendimiento general de su software, hasta el punto en que una elección aparentemente trivial puede significar la diferencia entre esperar 20 minutos para que el programa se inicie y tener una interfaz de usuario rápida y receptiva.
Por ejemplo, en un juego en 3D: si comienza con una simple lista plana de objetos para su gráfico de escena, verá un rendimiento extremadamente bajo para un número relativamente pequeño de objetos; pero si implementa una jerarquía de volumen (como un octree o BVH) y elimina partes del árbol mientras dibuja, verá un aumento masivo del rendimiento.
Cuando su diseño parece correcto, puede pasar a ...
Lógica de bajo nivel.
Los algoritmos de nivel inferior también pueden tener un impacto significativo. Al realizar el procesamiento de imágenes, por ejemplo, si lee la imagen en el orden incorrecto, experimentará desaceleraciones masivas a medida que se encuentre con constantes fallas de caché L2; reordenar sus operaciones podría significar un aumento de diez veces en el rendimiento.
En este punto, perfile y encuentre el lugar donde se pasa la mayor parte del tiempo del programa, y encuentre la manera de eliminarlo.
fuente
Primero, ejecute un generador de perfiles para averiguar dónde está gastando su tiempo su código.
Luego, mire esos lugares para ver cuáles parecen fáciles de optimizar.
Busque las soluciones más fáciles que obtendrán las mayores ganancias primero (opte por la fruta baja). No te preocupes demasiado por lo importante que es, precisamente. Si es fácil, arréglalo. Se sumará. 25 soluciones fáciles pueden ser más rápidas que 1 solución grande, y sus efectos acumulativos pueden ser mayores. Si es difícil, tome nota o presente un informe de error para poder priorizarlo más adelante. No se preocupe tanto por "grande" o "pequeño" en este punto, simplemente hágalo, hasta que llegue a las funciones que están usando muy poco tiempo. Una vez que haga esto, debe tener una mejor idea de cuáles de los otros problemas que ha descubierto podrían obtener las mayores ganancias por la menor inversión de tiempo.
No se olvide de seguir con la creación de perfiles después de sus correcciones como una especie de prueba de regresión, para verificar que sus cambios de rendimiento tuvieron los efectos que esperaba. Además, no olvide ejecutar su paquete de regresión para asegurarse de que no se haya roto ninguna funcionalidad. A veces, el mal rendimiento indica soluciones alternativas, e intentar arreglar el rendimiento interrumpirá la funcionalidad.
Las pequeñas funciones que no se pueden optimizar pero que utilizan mucho tiempo aún pueden ser pistas sobre dónde optimizar. ¿Por qué esa función se llama tanto? ¿Hay una función que llame a esa pequeña función que no necesita usarla tanto? ¿Se está duplicando el trabajo o se está haciendo un trabajo innecesario? Busque en la pila las veces que se llama hasta que esté seguro de que debería llamarse con tanta frecuencia, y vea si encuentra una función más grande con un algoritmo ineficiente.
Editado para agregar: dado que tiene una funcionalidad específica que lleva mucho tiempo, intente realizar los pasos anteriores con solo esa función específica ejecutándose aproximadamente 10 veces.
fuente
Es difícil de decir. Esto realmente depende de lo que esté haciendo el código. Ejecute una prueba de rendimiento, obtenga un perfil de rendimiento y observe y vea cuánto tiempo real se dedica en diversas áreas. Sus generalizaciones son ... generalizaciones y varían de un proyecto a otro.
Por ejemplo, el código que se llama más podría simplemente iniciar sesión en un archivo o consola. No tiene mucho sentido optimizar que si ya es una o dos líneas de código que no se pueden simplificar, y podría ser que cualquier esfuerzo por optimizar algo como esto no valga el costo de codificarlo realmente. El código menos llamado podría ser una consulta del tamaño de un monstruo utilizada en una función horriblemente compleja. Es posible que solo se llame a la función 100 veces durante una ejecución de ejecución completa (frente a 10000 para la instrucción de registro simple), pero si tarda 20 segundos por cada tiempo de llamada que se ejecuta, ¿tal vez es ahí donde debería comenzar la optimización? O podría ser al revés, con la consulta grande como la más llamada, y la declaración de registro solo llamó una por cada 100 consultas ...
Por lo general, no me preocupo por este tipo de cosas (hasta que necesito hacer ajustes de rendimiento) a menos que tenga una idea anticipada de lo que va a suceder.
fuente
Bueno, "nosotros" generalmente no optimizamos hasta que hay una obvia necesidad de optimización cuando algo es inaceptablemente lento.
Y cuando esta necesidad se manifiesta, generalmente lleva consigo buenos indicios de lo que requiere exactamente la optimización.
Entonces la respuesta es usual: "Depende".
fuente
Debe usar un generador de perfiles en un puñado de ejecuciones típicas y ver el tiempo total empleado en cada parte del código, sin importar cómo o con qué frecuencia haya llegado allí. La optimización de estas partes siempre debería aumentar la velocidad.
Dependiendo de qué tan bajo sea su lenguaje de implementación, también debe averiguar qué partes causan la mayoría de las fallas de caché. Consolidar el código de llamada ayudará aquí.
fuente
El problema es que la frase "donde se pasa la mayor parte del tiempo" es ambigua.
Si significa "donde se encuentra el contador de programas con mayor frecuencia", entonces he visto programas donde se pasó la mayor parte del tiempo en funciones de biblioteca matemática de comparación de cadenas, asignación de memoria. En otras palabras, funciones que el programador cotidiano nunca debe tocar.
Si significa "en qué parte del código del programador se ejecutan sentencias que consumen una gran fracción de tiempo", ese es un concepto más útil.
El problema con el concepto de "código que se llama más" es que la cantidad de tiempo que toma es el producto de la frecuencia con la que se llama y cuánto tiempo toma por llamada (incluidos los llamadas y las E / S). Dado que la cantidad de tiempo que lleva puede variar en varios órdenes de magnitud, la cantidad de veces que se llama no le dice qué tan problemático es. La función A se puede llamar 10 veces y tomar 0.1 segundos, mientras que la función B se puede llamar 1000 veces y tomar un microsegundo.
Una cosa que le dirá dónde buscar es esto: cada vez que una línea de código está causando tiempo , está en la pila . Entonces, por ejemplo, si una línea de código es un punto caliente, o si es una llamada a una función de biblioteca, o si es la vigésima llamada en un árbol de llamadas de 30 niveles, si es responsable del 20% del tiempo , entonces está en la pila el 20% del tiempo. Las muestras de la pila en tiempo aleatorio tendrán cada una un 20% de posibilidades de mostrarlo. Además, si se pueden tomar muestras durante la E / S, le mostrarán qué explica la E / S, que puede ser tan o más derrochador que los ciclos de CPU desperdiciados.
Y esto es totalmente independiente de cuántas veces se invoca.
fuente
Optimice dónde se pasa la mayor parte del tiempo, a menos que haya una razón particular para no hacerlo (es decir, la mayor parte del tiempo se gasta haciendo un procesamiento asincrónico que a los humanos realmente no les importa si termina en 5 minutos o 10 minutos). El código que se llama más, naturalmente, tenderá a acumular una porción relativamente grande del tiempo total transcurrido simplemente porque incluso los tiempos de ejecución cortos se suman cuando lo haces miles de veces.
fuente
Tienes que trabajar en el código que te toma más tiempo. Mejorar el código que solo representa un pequeño porcentaje del tiempo de ejecución solo puede brindarle una pequeña mejora.
¿Ha tomado medidas para saber qué código está tomando más tiempo?
fuente
Solía hacer benchmarking y marketing para un proveedor de supercomputadoras, por lo que vencer a la competencia rápidamente no era lo más importante, era lo ÚNICO. Ese tipo de resultado requiere que use una combinación de un algoritmo bueno y una estructura de datos que permita que las porciones más intensivas de CPU ejecuten un pico de manera eficiente. Eso significaba que tenía que tener una idea bastante buena de cuáles serían las operaciones más intensivas en cómputo y qué tipo de estructuras de datos les permitiría correr más rápido. Luego se trataba de construir la aplicación alrededor de esos núcleos optimizados / estructuras de datos.
En el sentido más general, el típico después del ajuste de hecho. Tu perfil, miras los puntos calientes y los puntos calientes que crees que puedes acelerar son aquellos en los que trabajas. Pero rara vez este enfoque le dará algo cercano a la implementación más rápida posible.
Sin embargo, en términos más generales (no obstante los errores algorítmicos), para las máquinas modernas tienes que pensar que el rendimiento está determinado por tres cosas: acceso a datos, acceso a datos y acceso a datos. Aprenda sobre la jerarquía de memoria (registros, cachés, TLB, páginas, etc.) y diseñe sus estructuras de datos para aprovecharlas lo mejor posible. En general, esto significa que desea poder ejecutar bucles dentro de una huella de memoria compacta. Si en su lugar solo escribe (o se le da) una aplicación y luego trata de optimizarla, por lo general está cargado con estructuras de datos que hacen un uso deficiente de la jerarquía de memoria, y cambiar las estructuras de datos generalmente implica un ejercicio de refactorización importante, por lo que a menudo atascado
fuente
Si desea obtener un retorno de su esfuerzo de optimización, debe mirar el código que toma más tiempo. Mi objetivo general es algo que toma al menos el 80% del tiempo. Generalmente apunto a una ganancia de rendimiento 10 veces mayor. Esto a veces requiere un cambio importante en cómo se diseña ese código. Este tipo de cambio te proporciona algo que funciona aproximadamente cuatro veces más rápido.
Mi mejor ganancia de rendimiento de todos los tiempos es reducir el tiempo de ejecución de 3 días a 9 minutos. El código que optimicé pasó de 3 días a 3 minutos. La aplicación que reemplazó esa aplicación redujo esto a 9 segundos, pero eso requirió un cambio de idioma y una reescritura completa.
Optimizar una aplicación que ya es rápida puede ser una tarea tonta. Todavía enfocaría el área tomando más tiempo. Si puede tomar algo usando el 10% del tiempo para regresar instantáneamente, todavía necesita el 90% del tiempo. Rápidamente alcanzas la regla de rendimientos decrecientes.
Dependiendo de lo que esté optimizando para la regla, todavía se aplica. Busque los principales usuarios de recursos y optimícelos. Si el recurso que está optimizando es el cuello de botella del sistema, es posible que todo lo que haga sea cambiar el cuello de botella a otro recurso.
fuente
Por lo general, serán funciones más carnosas en la mayoría de los casos, no las funciones llamadas con mayor frecuencia mil millones de veces en un bucle.
Cuando realiza perfiles basados en muestras (con una herramienta o a mano), a menudo los puntos de acceso más grandes estarán en pequeñas llamadas frondosas que hacen cosas simples, como una función para comparar dos enteros.
Esa función a menudo no se beneficiará de mucha, si es que hay alguna, optimización. Por lo menos, esos puntos de acceso granulares rara vez son la máxima prioridad. Es la función que llama a esa función de hoja la que podría ser la causa de problemas, o la función que llama a la función que llama a la función, como un algoritmo de clasificación subóptimo. Con buenas herramientas, puede profundizar de la persona que llama a la persona que llama, y también ver quién pasa más tiempo llamando a la persona que llama.
A menudo es un error obsesionarse con los callejeros y no mirar a las personas que llaman en el gráfico de llamadas en una sesión de creación de perfiles a menos que esté haciendo las cosas de manera muy ineficiente a nivel micro. De lo contrario, podría estar sudando demasiado las cosas pequeñas y perder de vista el panorama general. El solo hecho de tener un perfilador en la mano no lo protege de la obsesión por cosas triviales. Es solo un primer paso en la dirección correcta.
También debe asegurarse de que está perfilando las operaciones que se alinean con las cosas que los usuarios realmente quieren hacer, de lo contrario, ser totalmente disciplinado y científico en sus mediciones y puntos de referencia no tiene valor, ya que no se alinea con lo que los clientes hacen con el producto. Una vez tuve un colega que desactivó un algoritmo de subdivisión para subdividir un cubo en mil millones de facetas y se enorgulleció mucho de eso ... excepto que los usuarios no subdividen los cubos simples de 6 polígonos en mil millones facetas Todo se ralentizó cuando trató de funcionar en un modelo de automóvil de producción con más de 100,000 polígonos para subdividir, en cuyo punto ni siquiera podía hacer 2 o 3 niveles de subdivisión sin reducir la velocidad a un rastreo. En pocas palabras, escribió un código que estaba súper optimizado para tamaños de entrada poco realistas que no
Debe optimizar los casos de uso reales alineados con los intereses de sus usuarios o de lo contrario es peor que inútil, ya que todas esas optimizaciones que tienden a degradar al menos algo la capacidad de mantenimiento del código tienen pocos beneficios para el usuario y solo todos esos aspectos negativos para la base de código.
fuente