Estrategias de optimización del rendimiento de último recurso [cerrado]

609

Ya hay muchas preguntas de rendimiento en este sitio, pero se me ocurre que casi todas son muy específicas de problemas y bastante limitadas. Y casi todos repiten los consejos para evitar la optimización prematura.

Asumamos:

  • el código ya funciona correctamente
  • los algoritmos elegidos ya son óptimos para las circunstancias del problema
  • el código ha sido medido y las rutinas ofensivas han sido aisladas
  • todos los intentos de optimización también se medirán para garantizar que no empeoren las cosas

Lo que estoy buscando aquí son estrategias y trucos para exprimir hasta el último porcentaje en un algoritmo crítico cuando no queda nada más que hacer excepto lo que sea necesario.

Idealmente, trate de hacer que las respuestas sean independientes del lenguaje e indique cualquier inconveniente de las estrategias sugeridas cuando corresponda.

Agregaré una respuesta con mis propias sugerencias iniciales y espero cualquier otra cosa que la comunidad de Stack Overflow pueda pensar.

jerryjvl
fuente

Respuestas:

427

OK, estás definiendo el problema donde parece que no hay mucho margen de mejora. Eso es bastante raro, en mi experiencia. Traté de explicar esto en un artículo del Dr. Dobbs en noviembre de 1993, comenzando desde un programa no trivial convencionalmente bien diseñado sin desperdicio obvio y llevándolo a través de una serie de optimizaciones hasta que su tiempo de reloj de pared se redujo de 48 segundos a 1.1 segundos, y el tamaño del código fuente se redujo en un factor de 4. Mi herramienta de diagnóstico fue esta . La secuencia de cambios fue esta:

  • El primer problema encontrado fue el uso de grupos de listas (ahora llamados "iteradores" y "clases de contenedor") que representan más de la mitad del tiempo. Estos fueron reemplazados por un código bastante simple, reduciendo el tiempo a 20 segundos.

  • Ahora, el que más tiempo hace es construir más listas. Como porcentaje, antes no era tan grande, pero ahora se debe a que se eliminó el problema más grande. Encuentro una manera de acelerarlo, y el tiempo cae a 17 segundos.

  • Ahora es más difícil encontrar culpables obvios, pero hay algunos más pequeños sobre los que puedo hacer algo, y el tiempo se reduce a 13 segundos.

Ahora parece que he golpeado una pared. Las muestras me dicen exactamente lo que está haciendo, pero parece que no puedo encontrar nada que pueda mejorar. Luego reflexiono sobre el diseño básico del programa, sobre su estructura impulsada por las transacciones, y pregunto si toda la búsqueda en la lista que está haciendo es obligatoria según los requisitos del problema.

Luego me topé con un rediseño, donde el código del programa se genera realmente (a través de macros de preprocesador) a partir de un conjunto de fuentes más pequeño, y en el que el programa no está constantemente descubriendo cosas que el programador sabe que son bastante predecibles. En otras palabras, no "interprete" la secuencia de cosas que hacer, "compílela".

  • Ese rediseño se realiza, reduciendo el código fuente por un factor de 4, y el tiempo se reduce a 10 segundos.

Ahora, debido a que se está volviendo tan rápido, es difícil de probar, por lo que le doy 10 veces más trabajo, pero los siguientes tiempos se basan en la carga de trabajo original.

  • Más diagnóstico revela que está pasando tiempo en la gestión de colas. El forro interior reduce el tiempo a 7 segundos.

  • Ahora una gran toma de tiempo es la impresión de diagnóstico que había estado haciendo. Enjuague eso - 4 segundos.

  • Ahora, los que más tiempo toman son llamadas a malloc y gratuitas . Reciclar objetos - 2.6 segundos.

  • Continuando con la muestra, todavía encuentro operaciones que no son estrictamente necesarias: 1.1 segundos.

Factor de aceleración total: 43,6

Ahora no hay dos programas iguales, pero en el software que no es de juguete siempre he visto una progresión como esta. Primero obtienes las cosas fáciles, y luego las más difíciles, hasta que llegas a un punto de rendimientos decrecientes. Entonces, la información que obtenga puede conducir a un rediseño, comenzando una nueva ronda de aceleraciones, hasta que vuelva a alcanzar rendimientos decrecientes. Ahora bien, este es el punto en el que podría tener sentido a preguntarse si ++io i++o for(;;)o while(1)son más rápidos: el tipo de preguntas que veo tan a menudo en desbordamiento de pila.

PD: Me pregunto por qué no utilicé un generador de perfiles. La respuesta es que casi cada uno de estos "problemas" era un sitio de llamada de función, que apila las muestras con precisión. Los perfiladores, incluso hoy, apenas llegan a la idea de que las declaraciones y las instrucciones de llamada son más importantes de localizar y más fáciles de corregir que las funciones completas.

Realmente construí un generador de perfiles para hacer esto, pero para una verdadera intimidad con lo que está haciendo el código, no hay sustituto para meter los dedos en él. No es un problema que el número de muestras sea pequeño, porque ninguno de los problemas encontrados es tan pequeño que se pueden pasar por alto fácilmente.

AGREGADO: jerryjvl solicitó algunos ejemplos. Aquí está el primer problema. Consiste en una pequeña cantidad de líneas de código separadas, que juntas toman más de la mitad del tiempo:

 /* IF ALL TASKS DONE, SEND ITC_ACKOP, AND DELETE OP */
if (ptop->current_task >= ILST_LENGTH(ptop->tasklist){
. . .
/* FOR EACH OPERATION REQUEST */
for ( ptop = ILST_FIRST(oplist); ptop != NULL; ptop = ILST_NEXT(oplist, ptop)){
. . .
/* GET CURRENT TASK */
ptask = ILST_NTH(ptop->tasklist, ptop->current_task)

Estos estaban usando el ILST del grupo de listas (similar a una clase de lista). Se implementan de la manera habitual, con "ocultación de información", lo que significa que no se suponía que los usuarios de la clase tuvieran que preocuparse por cómo se implementaron. Cuando se escribieron estas líneas (de aproximadamente 800 líneas de código) no se pensó en la idea de que podrían ser un "cuello de botella" (odio esa palabra). Son simplemente la forma recomendada de hacer las cosas. Es fácil decir en retrospectiva que deberían haberse evitado, pero en mi experiencia todos los problemas de rendimiento son así. En general, es bueno tratar de evitar crear problemas de rendimiento. Es incluso mejor encontrar y arreglar los que se crean, aunque "deberían haberse evitado" (en retrospectiva).

Aquí está el segundo problema, en dos líneas separadas:

 /* ADD TASK TO TASK LIST */
ILST_APPEND(ptop->tasklist, ptask)
. . .
/* ADD TRANSACTION TO TRANSACTION QUEUE */
ILST_APPEND(trnque, ptrn)

Estas son listas de construcción agregando elementos a sus extremos. (La solución fue recopilar los elementos en matrices y crear las listas de una vez.) Lo interesante es que estas declaraciones solo cuestan (es decir, estaban en la pila de llamadas) 3/48 del tiempo original, por lo que no estaban en hecho un gran problema al principio . Sin embargo, después de eliminar el primer problema, costaban 3/20 del tiempo y ahora eran un "pez más grande". En general, así es como va.

Podría agregar que este proyecto fue destilado de un proyecto real en el que ayudé. En ese proyecto, los problemas de rendimiento fueron mucho más dramáticos (al igual que las aceleraciones), como llamar a una rutina de acceso a la base de datos dentro de un bucle interno para ver si una tarea estaba terminada.

REFERENCIA AGREGADA: El código fuente, tanto original como rediseñado, se puede encontrar en www.ddj.com , para 1993, en el archivo 9311.zip, archivos slug.asc y slug.zip.

EDITAR 26/11/2011: ahora hay un proyecto de SourceForge que contiene el código fuente en Visual C ++ y una descripción detallada de cómo se ajustó. Solo pasa por la primera mitad del escenario descrito anteriormente, y no sigue exactamente la misma secuencia, pero aún así obtiene una aceleración de orden de magnitud de 2-3.

Mike Dunlavey
fuente
3
Me encantaría leer algunos de los detalles de los pasos que describió anteriormente. ¿Es posible incluir algunos fragmentos de las optimizaciones para el sabor? (sin hacer el cargo demasiado tiempo?)
jerryjvl
8
... También escribí un libro que ahora está agotado, por lo que tendrá un precio ridículo en Amazon: "Creación de mejores aplicaciones" ISBN 0442017405. Esencialmente, el mismo material está en el primer capítulo.
Mike Dunlavey,
3
@ Mike Dunlavey, sugeriría decirle a Google que ya lo ha escaneado. Probablemente ya tengan un acuerdo con quien haya comprado su editor.
Thorbjørn Ravn Andersen
19
@ Thorbjørn: Solo para dar seguimiento, me conecté con GoogleBooks, completé todos los formularios y les envié una copia impresa. Recibí un correo electrónico preguntando si realmente poseía los derechos de autor. El editor Van Nostrand Reinhold, que fue comprado por International Thompson, que fue comprado por Reuters, y cuando trato de llamarlos o enviarles un correo electrónico es como un agujero negro. Así que está en el limbo: todavía no he tenido la energía para perseguirlo.
Mike Dunlavey
55
Enlace de Google Books: books.google.dk/books?id=8A43E1UFs_YC
Thorbjørn Ravn Andersen
188

Sugerencias:

  • Precalcule en lugar de volver a calcular : cualquier bucle o llamada repetida que contenga cálculos que tengan un rango relativamente limitado de entradas, considere hacer una búsqueda (matriz o diccionario) que contenga el resultado de ese cálculo para todos los valores en el rango válido de entradas Luego, utilice una búsqueda simple dentro del algoritmo.
    Desventajas : si realmente se usan pocos de los valores precalculados, esto puede empeorar las cosas, también la búsqueda puede requerir una memoria significativa.
  • No utilice métodos de biblioteca : la mayoría de las bibliotecas deben escribirse para funcionar correctamente en una amplia gama de escenarios, y realizar comprobaciones nulas de parámetros, etc. Al volver a implementar un método, puede eliminar mucha lógica que no se aplica en las circunstancias exactas en que lo está utilizando.
    Desventajas : escribir código adicional significa más área de superficie para errores.
  • Utilice métodos de biblioteca : para contradecirme, las bibliotecas de idiomas las escriben personas que son mucho más inteligentes que usted o yo; Lo más probable es que lo hicieron mejor y más rápido. No lo implemente usted mismo a menos que pueda hacerlo más rápido (es decir, ¡mida siempre!)
  • Truco : en algunos casos, aunque puede existir un cálculo exacto para su problema, es posible que no necesite "exacto", a veces una aproximación puede ser "lo suficientemente buena" y mucho más rápida en el trato. Pregúntese, ¿realmente importa si la respuesta sale en un 1%? 5%? incluso 10%?
    Desventajas : Bueno ... la respuesta no será exacta.
jerryjvl
fuente
32
La precomputación no siempre ayuda, e incluso puede doler a veces: si su tabla de búsqueda es demasiado grande, puede dañar el rendimiento de su caché.
Adam Rosenfield
37
Hacer trampa a menudo puede ser la victoria. Tuve un proceso de corrección de color que en el núcleo era un vector de 3 puntos punteado con una matriz de 3x3. La CPU tenía una matriz de multiplicación en hardware que omitía algunos de los términos cruzados y fue realmente rápida en comparación con todas las otras formas de hacerlo, pero solo admitía matrices 4x4 y 4 vectores de flotadores. Cambiar el código para transportar la ranura vacía adicional y convertir el cálculo a punto flotante desde un punto fijo permitió un resultado ligeramente menos preciso pero mucho más rápido.
RBerteig
66
El truco fue usar una matriz de multiplicación que dejó fuera algunos de los productos internos, lo que hizo posible implementar en microcódigo una sola instrucción de CPU que se completó más rápido que incluso la secuencia equivalente de instrucciones individuales. Es una trampa porque no obtiene la respuesta "correcta", solo una respuesta que es "lo suficientemente correcta".
RBerteig
66
@RBerteig: simplemente "lo suficientemente correcto" es una oportunidad de optimización que la mayoría de las personas pierden en mi experiencia.
Martin Thompson
55
No siempre se puede suponer que todos son más inteligentes que tú. Al final todos somos profesionales. Sin embargo, puede suponer que existe una biblioteca específica que utiliza y que ha alcanzado su entorno debido a su calidad, por lo tanto, la redacción de esta biblioteca debe ser muy exhaustiva, no puede hacerlo tan solo porque no está especializado en eso campo, y no inviertes el mismo tipo de tiempo en él. No porque seas menos inteligente. venga.
v.oddou
164

Cuando ya no pueda mejorar el rendimiento, vea si puede mejorar el rendimiento percibido .

Es posible que no pueda hacer que su algoritmo fooCalc sea más rápido, pero a menudo hay formas de hacer que su aplicación parezca más receptiva para el usuario.

Algunos ejemplos:

  • anticipando lo que el usuario va a solicitar y comenzar a trabajar en eso antes de eso
  • mostrar resultados a medida que entran, en lugar de todos a la vez al final
  • Medidor de progreso preciso

Esto no hará que su programa sea más rápido, pero podría hacer que sus usuarios estén más felices con la velocidad que tiene.

kenj0418
fuente
27
Una barra de progreso que se acelera al final puede percibirse como más rápida que una absolutamente precisa. En "Rethinking the Progress Bar" (2007), Harrison, Amento, Kuznetsov y Bell prueban varios tipos de barras en un grupo de usuarios, y analizan algunas formas de reorganizar las operaciones para que el progreso se perciba como más rápido.
Emil Vikström
99
naxa, la mayoría de las barras de progreso son falsas porque predecir múltiples pasos muy diferentes de un flujo en un solo porcentaje es difícil o, a veces, imposible. Basta con mirar todas esas barras que se atascan al 99% :-(
Emil Vikström
138

Paso la mayor parte de mi vida solo en este lugar. Los trazos generales son ejecutar su generador de perfiles y hacer que registre:

  • La caché falla . El caché de datos es la fuente número 1 de puestos en la mayoría de los programas. Mejore la tasa de aciertos de caché reorganizando las estructuras de datos ofensivas para tener una mejor localidad; empacar estructuras y tipos numéricos para eliminar bytes desperdiciados (y, por lo tanto, desperdicios de caché perdidos); recupere datos siempre que sea posible para reducir los puestos.
  • Tiendas de carga y golpe . Los supuestos del compilador sobre el alias de puntero, y los casos en que los datos se mueven entre conjuntos de registros desconectados a través de la memoria, pueden causar un cierto comportamiento patológico que hace que toda la tubería de la CPU se borre en una operación de carga. Encuentre lugares donde se floten flotadores, vectores e ints entre sí y elimínelos. Use __restrictgenerosamente para prometerle al compilador sobre alias.
  • Operaciones microcodificadas . La mayoría de los procesadores tienen algunas operaciones que no se pueden canalizar, sino que ejecutan una pequeña subrutina almacenada en la ROM. Los ejemplos en el PowerPC son multiplicación entera, división y cambio por cantidad variable. El problema es que toda la tubería se detiene mientras se ejecuta esta operación. Intente eliminar el uso de estas operaciones o al menos desglosarlas en sus operaciones canalizadas constituyentes para que pueda obtener el beneficio del despacho superescalar en lo que sea que esté haciendo el resto de su programa.
  • La rama predice mal . Estos también vacían la tubería. Encuentre casos en los que la CPU pasa mucho tiempo rellenando la tubería después de una bifurcación, y use sugerencias de bifurcación si están disponibles para que pueda predecir correctamente con mayor frecuencia. O mejor aún, reemplace las ramas con movimientos condicionales siempre que sea posible, especialmente después de las operaciones de coma flotante porque su tubería suele ser más profunda y leer las banderas de condición después de fcmp puede causar un bloqueo.
  • Operaciones secuenciales de coma flotante . Haz estos SIMD.

Y una cosa más que me gusta hacer:

  • Configure su compilador para generar listados de ensamblaje y observe lo que emite para las funciones de punto de acceso en su código. ¿Todas esas optimizaciones inteligentes que "un buen compilador debería poder hacer por usted automáticamente"? Lo más probable es que tu compilador real no los haga. He visto a GCC emitir verdaderamente código WTF.
Crashworks
fuente
8
Principalmente uso Intel VTune y PIX. No tengo idea de si pueden adaptarse a C #, pero realmente una vez que tienes esa capa de abstracción JIT, la mayoría de estas optimizaciones están fuera de tu alcance, excepto para mejorar la localidad de la caché y tal vez evitar algunas ramas.
Crashworks
66
Aun así, verificar la salida posterior al JIT puede ayudar a determinar si hay construcciones que simplemente no se optimizan bien durante la etapa JIT ... la investigación nunca puede doler, incluso si resulta un callejón sin salida.
jerryjvl
55
Creo que mucha gente, incluyéndome a mí, estaría interesada en esta "asamblea wtf" producida por gcc. El tuyo parece un trabajo muy interesante :)
BlueRaja - Danny Pflughoeft
1
Examples on the PowerPC ...<- Es decir, algunas implementaciones de PowerPC. PowerPC es un ISA, no una CPU.
Billy ONeal
1
@BillyONeal Incluso en hardware moderno x86, imul puede detener la tubería; consulte el "Manual de referencia de optimización de arquitecturas Intel® 64 e IA-32" §13.3.2.3: "La instrucción de multiplicación de enteros tarda varios ciclos en ejecutarse. Se canalizan de manera que una instrucción de multiplicación de enteros y otra instrucción de latencia larga puedan avanzar en el fase de ejecución. Sin embargo, las instrucciones de multiplicación de enteros bloquearán la emisión de otras instrucciones de un solo ciclo debido al requisito de orden del programa ". Es por eso que generalmente es mejor usar tamaños de matriz alineados con palabras y lea.
Crashworks
78

¡Lanza más hardware!

sisve
fuente
30
más hardware no siempre es una opción cuando tienes un software que se espera que se ejecute en hardware que ya está en el campo.
Doug T.
76
No es una respuesta muy útil para alguien que hace software para el consumidor: el cliente no querrá escucharte decir: "compra una computadora más rápida". Especialmente si estás escribiendo software para apuntar a algo como una consola de videojuegos.
Crashworks
19
@Crashworks, o para el caso, un sistema integrado. Cuando la última característica es, finalmente, y el primer lote de placas ya están hiladas no es el momento para descubrir que debería haber utilizado una CPU más rápida en el primer lugar ...
RBerteig
71
Una vez tuve que depurar un programa que tenía una gran pérdida de memoria: su tamaño de VM creció en aproximadamente 1Mb por hora. Un colega bromeó diciendo que todo lo que necesitaba hacer era agregar memoria a un ritmo constante . :)
j_random_hacker
99
Más hardware: ah sí, la línea de vida del desarrollador mediocre. ¡No sé cuántas veces he escuchado "agregar otra máquina y duplicar la capacidad!"
Olof Forshell
58

Mas sugerencias:

  • Evite las E / S : cualquier E / S (disco, red, puertos, etc.) siempre será mucho más lenta que cualquier código que realice cálculos, por lo tanto, elimine cualquier E / S que no necesite estrictamente.

  • Mover E / S por adelantado : cargue todos los datos que necesitará para un cálculo por adelantado, de modo que no haya esperas repetidas de E / S dentro del núcleo de un algoritmo crítico (y tal vez como resultado repetido disco busca, al cargar todos los datos en un solo golpe puede evitar la búsqueda).

  • Retraso de E / S : no escriba sus resultados hasta que termine el cálculo, guárdelos en una estructura de datos y luego bótelos de una vez al final cuando el trabajo duro haya terminado.

  • E / S roscada : para aquellos lo suficientemente atrevidos, combine 'E / S por adelantado' o 'Retraso de E / S' con el cálculo real moviendo la carga a un hilo paralelo, de modo que mientras carga más datos pueda trabajar en un cálculo de los datos que ya tiene, o mientras calcula el siguiente lote de datos, puede escribir simultáneamente los resultados del último lote.

Peter Mortensen
fuente
3
Tenga en cuenta que "mover el IO a un subproceso paralelo" debe hacerse como IO asíncrono en muchas plataformas (por ejemplo, Windows NT).
Billy ONeal
2
La E / S es realmente un punto crítico, porque es lenta y tiene latencias enormes, y puede ser más rápido con este consejo, pero aún tiene fallas fundamentales: los puntos son la latencia (que debe estar oculta) y la sobrecarga de syscall ( que debe reducirse reduciendo el número de llamadas de E / S). El mejor consejo es: usar mmap()para entrada, hacer madvise()llamadas apropiadas y usar aio_write()para escribir grandes porciones de salida (= algunos MiB).
cmaster - reinstalar a monica
1
Esta última opción es bastante fácil de implementar en Java, especialmente. Dio enormes aumentos de rendimiento para las aplicaciones que he escrito. Otro punto importante (más que mover E / S por adelantado) es hacer que sea E / S SECUENCIAL y de bloque grande. Muchas lecturas pequeñas son mucho más caras que 1 grande, debido al tiempo de búsqueda de disco.
BobMcGee
En un momento, hice trampa para evitar E / S, moviendo temporalmente todos los archivos a un disco RAM antes del cálculo y volviéndolos a mover después. Esto está sucio, pero podría ser útil en una situación en la que no controlas la lógica que hace las llamadas de E / S.
MD
48

Dado que muchos de los problemas de rendimiento involucran problemas de la base de datos, le daré algunas cosas específicas que debe tener en cuenta al ajustar consultas y procedimientos almacenados.

Evite los cursores en la mayoría de las bases de datos. Evite bucles también. La mayoría de las veces, el acceso a los datos debe basarse en el conjunto, no en el procesamiento de registros. Esto incluye no reutilizar un solo procedimiento almacenado de registros cuando desee insertar 1,000,000 de registros a la vez.

Nunca use select *, solo devuelva los campos que realmente necesita. Esto es especialmente cierto si hay uniones ya que los campos de unión se repetirán y, por lo tanto, causarán una carga innecesaria tanto en el servidor como en la red.

Evite el uso de subconsultas correlacionadas. Utilice combinaciones (incluidas combinaciones a tablas derivadas cuando sea posible) (Sé que esto es cierto para Microsoft SQL Server, pero pruebe los consejos cuando utilice un backend diferente).

Índice, índice, índice. Y obtenga esas estadísticas actualizadas si corresponde a su base de datos.

Haga la consulta sargable . Es decir, evitar cosas que hacen imposible usar los índices, como el uso de un comodín en el primer carácter de una cláusula like o una función en la unión o como la parte izquierda de una instrucción where.

Use los tipos de datos correctos. Es más rápido hacer cálculos de fechas en un campo de fecha que tener que intentar convertir un tipo de datos de cadena en un tipo de datos de fecha, luego hacer el cálculo.

¡Nunca ponga un bucle de ningún tipo en un gatillo!

La mayoría de las bases de datos tienen una forma de verificar cómo se realizará la ejecución de la consulta. En Microsoft SQL Server esto se llama plan de ejecución. Verifíquelos primero para ver dónde se encuentran las áreas problemáticas.

Considere la frecuencia con la que se ejecuta la consulta, así como el tiempo que tarda en ejecutarse al determinar qué debe optimizarse. A veces puede obtener más rendimiento de un ligero ajuste a una consulta que se ejecuta millones de veces al día de lo que puede eliminar el tiempo de una consulta de larga duración que solo se ejecuta una vez al mes.

Use algún tipo de herramienta de perfil para averiguar qué se está enviando realmente desde y hacia la base de datos. Recuerdo una vez en el pasado en la que no podíamos entender por qué la página tardaba tanto en cargarse cuando el procedimiento almacenado era rápido y descubría que la página web solicitaba la consulta muchas veces en lugar de una.

El generador de perfiles también lo ayudará a encontrar quién está bloqueando a quién. Algunas consultas que se ejecutan rápidamente mientras se ejecutan solas pueden volverse realmente lentas debido a bloqueos de otras consultas.

HLGEM
fuente
29

El factor limitante más importante hoy en día es la memoria limitada bandwitdh . Los multinúcleos solo empeoran esto, ya que el ancho de banda se comparte entre los núcleos. Además, el área limitada de chips dedicada a la implementación de cachés también se divide entre los núcleos y los hilos, lo que empeora aún más este problema. Finalmente, la señalización entre chips necesaria para mantener coherentes los diferentes cachés también aumenta con un mayor número de núcleos. Esto también agrega una penalización.

Estos son los efectos que debes gestionar. A veces a través de la micro gestión de su código, pero a veces a través de una cuidadosa consideración y refactorización.

Muchos comentarios ya mencionan el código amigable de caché. Hay al menos dos sabores distintos de esto:

  • Evite las latencias de recuperación de memoria.
  • Baja presión del bus de memoria (ancho de banda).

El primer problema tiene que ver específicamente con hacer que sus patrones de acceso a datos sean más regulares, permitiendo que el prefetcher de hardware funcione de manera eficiente. Evite la asignación dinámica de memoria que extiende sus objetos de datos en la memoria. Use contenedores lineales en lugar de listas vinculadas, hashes y árboles.

El segundo problema tiene que ver con mejorar la reutilización de datos. Modifique sus algoritmos para trabajar en subconjuntos de sus datos que se ajusten a la memoria caché disponible, y reutilice esos datos tanto como sea posible mientras todavía está en la memoria caché.

Empacar los datos más estrictos y asegurarse de usar todos los datos en las líneas de caché en los bucles activos ayudará a evitar estos otros efectos y permitirá ajustar datos más útiles en el caché.

Mats N
fuente
25
  • ¿Qué hardware estás ejecutando? ¿Se pueden usar optimizaciones específicas de la plataforma (como la vectorización)?
  • ¿Puedes conseguir un mejor compilador? Por ejemplo, ¿cambiar de GCC a Intel?
  • ¿Puedes hacer que tu algoritmo se ejecute en paralelo?
  • ¿Se pueden reducir los errores de caché reorganizando los datos?
  • ¿Se puede deshabilitar afirmaciones?
  • Micro-optimice para su compilador y plataforma. En el estilo de, "en caso de que sea así, ponga primero la declaración más común"
Johan Kotlinski
fuente
44
Debería ser "cambiar de GCC a LLVM" :)
Zifre
44
¿Puedes hacer que tu algoritmo se ejecute en paralelo? - lo contrario también se aplica
justin
44
Es cierto que reducir la cantidad de hilos puede ser una optimización igualmente buena
Johan Kotlinski,
re: micro-optimización: si verifica la salida asm del compilador, a menudo puede ajustar la fuente para mantenerla en la mano para producir una mejor asm. Vea ¿Por qué este código C ++ es más rápido que mi ensamblaje escrito a mano para probar la conjetura de Collatz? para obtener más información sobre cómo ayudar o vencer al compilador en x86 moderno.
Peter Cordes
17

Aunque me gusta la respuesta de Mike Dunlavey, de hecho es una gran respuesta con un ejemplo de apoyo, creo que podría expresarse de manera muy simple:

Averigüe primero qué toma la mayor cantidad de tiempo y comprenda por qué.

Es el proceso de identificación de los cerdos del tiempo lo que le ayuda a comprender dónde debe refinar su algoritmo. Esta es la única respuesta agnóstica de lenguaje integral que puedo encontrar a un problema que ya se supone que está completamente optimizado. También suponiendo que desea ser independiente de la arquitectura en su búsqueda de velocidad.

Entonces, si bien el algoritmo puede optimizarse, su implementación puede no serlo. La identificación le permite saber qué parte es cuál: algoritmo o implementación. Por lo tanto, el que más tiempo ocupa es su principal candidato para la revisión. Pero dado que dice que desea exprimir los últimos%, es posible que también desee examinar las partes menores, las partes que no ha examinado tan de cerca al principio.

Por último, un poco de prueba y error con cifras de rendimiento sobre diferentes formas de implementar la misma solución, o algoritmos potencialmente diferentes, puede aportar información que ayude a identificar el desperdicio de tiempo y el ahorro de tiempo.

HPH, muévete en voz alta.

minuto
fuente
16

Probablemente debería considerar la "perspectiva de Google", es decir, determinar cómo su aplicación puede volverse paralelizada y concurrente en gran medida, lo que inevitablemente también significará en algún momento considerar la distribución de su aplicación en diferentes máquinas y redes, para que pueda escalar idealmente casi linealmente con el hardware que le arrojas.

Por otro lado, la gente de Google también es conocida por aportar una gran cantidad de mano de obra y recursos para resolver algunos de los problemas en los proyectos, herramientas e infraestructura que están utilizando, como por ejemplo la optimización de todo el programa para gcc al tener un equipo dedicado de ingenieros piratería interna de gcc para prepararlo para los escenarios de casos de uso típicos de Google.

Del mismo modo, perfilar una aplicación ya no significa simplemente perfilar el código del programa, sino también todos sus sistemas e infraestructura circundantes (redes de pensamiento, conmutadores, servidores, matrices RAID) para identificar redundancias y potencial de optimización desde el punto de vista de un sistema.

ninguno
fuente
15
  • Rutinas en línea (eliminar llamada / retorno y empuje de parámetros)
  • Intente eliminar pruebas / interruptores con búsquedas de tabla (si son más rápidas)
  • Desenrolle los bucles (dispositivo de Duff) hasta el punto en que simplemente quepan en el caché de la CPU
  • Localice el acceso a la memoria para no volar su caché
  • Localice los cálculos relacionados si el optimizador ya no está haciendo eso
  • Elimine los invariantes de bucle si el optimizador ya no lo está haciendo.
pedestal
fuente
2
El dispositivo IIRC Duff rara vez es más rápido. Solo cuando la operación es muy corta (como una pequeña expresión matemática)
BCS
12
  • Cuando llegas al punto de que estás usando algoritmos eficientes, es una cuestión de qué necesitas más velocidad o memoria . Use el almacenamiento en caché para "pagar" en la memoria para obtener más velocidad o use los cálculos para reducir la huella de la memoria.
  • Si es posible (y más rentable) arroje hardware al problema : una CPU más rápida, más memoria o HD podrían resolver el problema más rápido que tratar de codificarlo.
  • Utilice la paralelización si es posible: ejecute parte del código en varios subprocesos.
  • Use la herramienta adecuada para el trabajo . algunos lenguajes de programación crean código más eficiente, el uso de código administrado (es decir, Java / .NET) acelera el desarrollo, pero los lenguajes de programación nativos crean un código de ejecución más rápido.
  • Micro optimizar . Solo si fuera aplicable, puede usar un ensamblaje optimizado para acelerar pequeños fragmentos de código, el uso de optimizaciones SSE / vectoriales en los lugares correctos puede aumentar considerablemente el rendimiento.
Dror Helper
fuente
12

Divide y conquistaras

Si el conjunto de datos que se procesa es demasiado grande, repítalo en trozos. Si ha hecho bien su código, la implementación debería ser fácil. Si tiene un programa monolítico, ahora lo sabe mejor.

MPelletier
fuente
99
+1 para el sonido de "golpe" del matamoscas que escuché mientras leía la última oración.
Bryan Boettcher
11

En primer lugar, como se mencionó en varias respuestas anteriores, aprenda lo que muerde su rendimiento, ya sea memoria o procesador o red o base de datos o algo más. Dependiendo de eso ...

  • ... si es memoria, encuentre uno de los libros escritos hace mucho tiempo por Knuth, uno de la serie "El arte de la programación de computadoras". Lo más probable es que se trate de ordenar y buscar: si mi memoria está mal, tendrás que averiguar en qué habla sobre cómo lidiar con el almacenamiento lento de datos en cinta. Transforme mentalmente su par de memoria / cinta en su par de caché / memoria principal (o en un par de caché L1 / L2) respectivamente. Estudie todos los trucos que describe: si no encuentra algo que resuelva su problema, contrate a un informático profesional para que realice una investigación profesional. Si su problema de memoria es casual con FFT (la memoria caché falla en los índices de inversión de bits al hacer mariposas radix-2), entonces no contrate a un científico; en su lugar, optimice manualmente los pases uno por uno hasta que ' re ganar o llegar al callejón sin salida. Mencionasteexprimir hasta el último porcentaje correcto? Si son pocos , lo más probable es que ganes.

  • ... si es procesador, cambie al lenguaje ensamblador. Estudie la especificación del procesador: lo que requiere ticks , VLIW, SIMD. Las llamadas a funciones son muy probablemente reemplazables. Aprenda las transformaciones de bucle: canalización, desenrollado. Las multiplicaciones y divisiones pueden ser reemplazables / interpoladas con cambios de bits (las multiplicaciones por enteros pequeños pueden ser reemplazables con adiciones). Pruebe trucos con datos más cortos: si tiene suerte, una instrucción con 64 bits podría ser reemplazable por dos en 32 o incluso 4 en 16 u 8 en 8 bits. Prueba también por más tiempodatos, por ejemplo, sus cálculos flotantes pueden ser más lentos que los dobles en un procesador particular. Si tienes cosas trigonométricas, lucha con tablas precalculadas; También tenga en cuenta que el seno de valor pequeño podría reemplazarse con ese valor si la pérdida de precisión se encuentra dentro de los límites permitidos.

  • ... si es una red, piense en comprimir los datos que pasa por encima. Reemplazar transferencia XML con binario. Protocolos de estudio. Pruebe UDP en lugar de TCP si de alguna manera puede manejar la pérdida de datos.

  • ... si se trata de una base de datos, vaya a cualquier foro de base de datos y pida consejo Cuadrícula de datos en memoria, optimización del plan de consulta, etc., etc.

HTH :)

mosquito
fuente
9

Almacenamiento en caché! Una forma económica (en esfuerzo del programador) de hacer casi cualquier cosa más rápido es agregar una capa de abstracción de almacenamiento en caché a cualquier área de movimiento de datos de su programa. Ya sea E / S o simplemente pasar / crear objetos o estructuras. A menudo es fácil agregar cachés a clases de fábrica y lectores / escritores.

A veces, el caché no te dará mucho, pero es un método fácil para agregar el almacenamiento en caché por todas partes y luego deshabilitarlo donde no ayuda. A menudo he encontrado que esto gana un gran rendimiento sin tener que microanalizar el código.

Killroy
fuente
8

Creo que esto ya se ha dicho de una manera diferente. Pero cuando se trata de un algoritmo de procesador intensivo, debe simplificar todo dentro del bucle más interno a expensas de todo lo demás.

Eso puede parecer obvio para algunos, pero es algo en lo que trato de enfocarme independientemente del idioma con el que estoy trabajando. Si se trata de bucles anidados, por ejemplo, y encuentra la oportunidad de reducir el nivel de un código, en algunos casos puede acelerar drásticamente su código. Como otro ejemplo, hay algunas pequeñas cosas en las que pensar, como trabajar con números enteros en lugar de variables de coma flotante siempre que puede, y usar la multiplicación en lugar de la división siempre que puede. Nuevamente, estas son cosas que deben considerarse para su ciclo más interno.

A veces puede encontrar el beneficio de realizar sus operaciones matemáticas en un número entero dentro del bucle interno, y luego reducirlo a una variable de punto flotante con la que pueda trabajar después. Ese es un ejemplo de sacrificar la velocidad en una sección para mejorar la velocidad en otra, pero en algunos casos la recompensa puede valer la pena.

Steve Wortham
fuente
8

Pasé algún tiempo trabajando en la optimización de los sistemas empresariales de cliente / servidor que operan en redes de bajo ancho de banda y larga latencia (por ejemplo, satélite, remoto, en alta mar), y pude lograr algunas mejoras dramáticas de rendimiento con un proceso bastante repetible.

  • Medida : Comience por comprender la capacidad y la topología subyacentes de la red. Hablar con las personas de redes relevantes en el negocio y utilizar herramientas básicas como ping y traceroute para establecer (como mínimo) la latencia de la red desde la ubicación de cada cliente, durante los períodos operativos típicos. Luego, tome medidas precisas de tiempo de funciones específicas del usuario final que muestren los síntomas problemáticos. Registre todas estas medidas, junto con sus ubicaciones, fechas y horas. Considere incorporar la funcionalidad de "prueba de rendimiento de red" del usuario final en su aplicación cliente, permitiendo que sus usuarios avanzados participen en el proceso de mejora; empoderarlos de esta manera puede tener un gran impacto psicológico cuando se trata de usuarios frustrados por un sistema de bajo rendimiento.

  • Analizar : utilizando cualquiera y todos los métodos de registro disponibles para establecer exactamente qué datos se transmiten y reciben durante la ejecución de las operaciones afectadas. Idealmente, su aplicación puede capturar datos transmitidos y recibidos tanto por el cliente como por el servidor. Si estos incluyen marcas de tiempo también, incluso mejor. Si no hay suficiente registro disponible (p. Ej., Sistema cerrado o incapacidad para implementar modificaciones en un entorno de producción), use un sniffer de red y asegúrese de comprender realmente lo que está sucediendo a nivel de red.

  • Caché : busque casos en los que los datos estáticos o modificados con poca frecuencia se transmitan de forma repetitiva y considere una estrategia de almacenamiento en caché adecuada. Los ejemplos típicos incluyen valores de "lista de selección" u otras "entidades de referencia", que pueden ser sorprendentemente grandes en algunas aplicaciones comerciales. En muchos casos, los usuarios pueden aceptar que deben reiniciar o actualizar la aplicación para actualizar los datos que se actualizan con poca frecuencia, especialmente si puede ahorrar un tiempo considerable desde la visualización de los elementos de la interfaz de usuario de uso común. Asegúrese de comprender el comportamiento real de los elementos de almacenamiento en caché ya implementados: muchos métodos de almacenamiento en caché comunes (por ejemplo, HTTP ETag) todavía requieren un viaje de ida y vuelta de red para garantizar la coherencia, y donde la latencia de red es costosa, puede evitarlo por completo con Un enfoque diferente de almacenamiento en caché.

  • Paralelo : busque transacciones secuenciales que lógicamente no se deban emitir de forma estricta y vuelva a trabajar el sistema para emitirlas en paralelo. Me ocupé de un caso en el que una solicitud de extremo a extremo tenía un retraso de red inherente de ~ 2 segundos, que no era un problema para una sola transacción, pero cuando se requerían 6 viajes de ida y vuelta secuenciales de 2 segundos antes de que el usuario recuperara el control de la aplicación del cliente , se convirtió en una gran fuente de frustración. Descubrir que estas transacciones eran de hecho independientes les permitió ejecutarse en paralelo, reduciendo el retraso del usuario final a muy cerca del costo de un solo viaje de ida y vuelta.

  • Combinar : cuando las solicitudes secuenciales deben ejecutarse secuencialmente, busque oportunidades para combinarlas en una sola solicitud más completa. Los ejemplos típicos incluyen la creación de nuevas entidades, seguido de solicitudes para relacionar esas entidades con otras entidades existentes.

  • Comprimir : busque oportunidades para aprovechar la compresión de la carga útil, ya sea reemplazando una forma textual por una binaria, o utilizando la tecnología de compresión real. Muchas pilas de tecnología moderna (es decir, dentro de una década) admiten esto de forma casi transparente, así que asegúrese de que esté configurado. A menudo me ha sorprendido el impacto significativo de la compresión, donde parecía claro que el problema era fundamentalmente la latencia en lugar del ancho de banda, descubriendo después de que permitía que la transacción encajara en un solo paquete o, de lo contrario, evitara la pérdida de paquetes y, por lo tanto, tuviera un tamaño excesivo impacto en el rendimiento.

  • Repita : regrese al principio y vuelva a medir sus operaciones (en los mismos lugares y horarios) con las mejoras implementadas, registre e informe sus resultados. Como con toda optimización, algunos problemas pueden haberse resuelto exponiendo otros que ahora dominan.

En los pasos anteriores, me concentro en el proceso de optimización relacionado con la aplicación, pero, por supuesto, debe asegurarse de que la red subyacente esté configurada de la manera más eficiente para admitir su aplicación también. Involucre a los especialistas en redes en el negocio y determine si pueden aplicar mejoras de capacidad, QoS, compresión de red u otras técnicas para abordar el problema. Por lo general, no entenderán las necesidades de su aplicación, por lo que es importante que esté equipado (después del paso Analizar) para discutirlo con ellos, y también para exponer el caso comercial de los costos en los que les va a pedir que incurran. . Encontré casos en los que la configuración de red errónea provocó que los datos de las aplicaciones se transmitieran a través de un enlace de satélite lento en lugar de un enlace terrestre, simplemente porque estaba usando un puerto TCP que no era "bien conocido" por los especialistas en redes; Obviamente, la rectificación de un problema como este puede tener un impacto dramático en el rendimiento, sin necesidad de ningún código de software o cambios de configuración necesarios.

Palmadita
fuente
7

Muy difícil dar una respuesta genérica a esta pregunta. Realmente depende del dominio del problema y la implementación técnica. Una técnica general que es bastante neutral en cuanto al lenguaje: identifique puntos de acceso de código que no se pueden eliminar y optimice manualmente el código del ensamblador.

dschwarz
fuente
7

El último% es algo muy dependiente de la CPU y la aplicación ...

  • las arquitecturas de caché difieren, algunos chips tienen RAM en chip que puede mapear directamente, los ARM (a veces) tienen una unidad vectorial, SH4 es un código de operación de matriz útil. ¿Hay una GPU ? Tal vez un sombreador es el camino a seguir. Los TMS320 son muy sensibles a las ramas dentro de los bucles (por lo tanto, separe los bucles y las condiciones de movimiento fuera si es posible).

La lista continúa ... Pero este tipo de cosas realmente son el último recurso ...

Compile para x86 y ejecute Valgrind / Cachegrind contra el código para obtener un perfil de rendimiento adecuado. O CCStudio de Texas Instruments tiene un dulce perfilador. Entonces realmente sabrás dónde concentrarte ...

Peter Mortensen
fuente
7

Did you know that a CAT6 cable is capable of 10x better shielding off extrenal inteferences than a default Cat5e UTP cable?

Para cualquier proyecto que no esté fuera de línea, mientras tenga el mejor software y el mejor hardware, si su rendimiento total es débil, esa delgada línea va a comprimir los datos y le dará demoras, aunque sea en milisegundos ... pero si está hablando de las últimas caídas , eso son algunas gotas obtenidas, 24/7 para cualquier paquete enviado o recibido.

Sam
fuente
7

No es tan profundo o complejo como las respuestas anteriores, pero aquí va: (estos son más nivel principiante / intermedio)

  • obvio: seco
  • ejecutar bucles hacia atrás para que siempre se compare con 0 en lugar de una variable
  • use operadores bit a bit siempre que pueda
  • romper el código repetitivo en módulos / funciones
  • objetos de caché
  • las variables locales tienen una ligera ventaja de rendimiento
  • limitar la manipulación de cadenas tanto como sea posible
Aaron
fuente
44
Acerca del bucle hacia atrás: sí, la comparación para el final del bucle será más rápida. Sin embargo, normalmente utiliza la variable para indexar en la memoria, y el acceso invertido puede ser contraproducente debido a errores frecuentes de caché (sin captación previa).
Andreas Reiff
1
AFAIK, en la mayoría de los casos, cualquier optimizador razonable funcionará bien con los bucles, sin que el programador tenga que ejecutar explícitamente en reversa. O bien el optimizador revertirá el bucle en sí mismo, o tiene otra forma que es igualmente buena. He notado una salida ASM idéntica para (lo que es relativamente simple) bucles escritos tanto ascendente como máximo y descendente vs 0. Claro, mis días de Z80 me tienen la costumbre de escribir reflexivamente bucles hacia atrás, pero sospecho que mencionarlo a los novatos suele ser un arenque rojo / optimización prematura, cuando el código legible y el aprendizaje de prácticas más importantes deben ser prioridades.
underscore_d
Por el contrario, ejecutar un bucle hacia atrás será más lento en los idiomas de nivel inferior porque en una guerra entre la comparación a cero más la resta adicional frente a una comparación de entero único, la comparación de entero único es más rápida. En lugar de disminuir, puede tener un puntero a la dirección inicial en la memoria y un puntero a la dirección final en la memoria. Luego, incremente el puntero de inicio hasta que sea igual al puntero de fin. Esto eliminará la operación de compensación de memoria adicional en el código de ensamblaje, lo que demuestra mucho más rendimiento.
Jack Giffin
5

Imposible decirlo. Depende de cómo se vea el código. Si podemos suponer que el código ya existe, entonces podemos simplemente mirarlo y descubrir a partir de eso, cómo optimizarlo.

Mejor localidad de caché, desenrollamiento de bucle. Intente eliminar cadenas de dependencia largas para obtener un mejor paralelismo a nivel de instrucción. Prefiere movimientos condicionales sobre ramas cuando sea posible. Explote las instrucciones SIMD cuando sea posible.

Comprenda lo que hace su código y comprenda el hardware en el que se ejecuta. Luego, se vuelve bastante simple determinar lo que debe hacer para mejorar el rendimiento de su código. Ese es realmente el único consejo realmente general que se me ocurre.

Bueno, eso y "Mostrar el código en SO y pedir consejos de optimización para ese fragmento de código específico".

jalf
fuente
5

Si un mejor hardware es una opción, entonces definitivamente ve por eso. De otra manera

  • Compruebe que está utilizando las mejores opciones de compilador y enlazador.
  • Si la rutina de punto de acceso en una biblioteca diferente a la persona que llama con frecuencia, considere moverla o clonarla al módulo de llamadas. Elimina parte de la sobrecarga de la llamada y puede mejorar las visitas a la memoria caché (vea cómo AIX vincula strcpy () estáticamente en objetos compartidos vinculados por separado). Por supuesto, esto también podría disminuir los éxitos de caché, por lo que una medida.
  • Vea si hay alguna posibilidad de usar una versión especializada de la rutina de puntos de acceso. Lo malo es mantener más de una versión.
  • Mira al ensamblador. Si cree que podría ser mejor, considere por qué el compilador no se dio cuenta de esto y cómo podría ayudarlo.
  • Considere: ¿realmente está utilizando el mejor algoritmo? ¿Es el mejor algoritmo para su tamaño de entrada?
mealnor
fuente
Agregaría a su primer par .: no olvide desactivar toda la información de depuración en las opciones de su compilador .
varnie
5

La forma de google es una opción "Cachéarla ... Siempre que sea posible no toque el disco"

asyncwait
fuente
5

Aquí hay algunas técnicas de optimización rápidas y sucias que uso. Considero que esta es una optimización de 'primer paso'.

Aprenda dónde se gasta el tiempo. Descubra exactamente lo que le toma el tiempo. ¿Es el archivo IO? ¿Es tiempo de CPU? ¿Es la red? ¿Es la base de datos? Es inútil optimizar para IO si ese no es el cuello de botella.

Conozca su entorno Saber dónde optimizar normalmente depende del entorno de desarrollo. En VB6, por ejemplo, pasar por referencia es más lento que pasar por valor, pero en C y C ++, por referencia es mucho más rápido. En C, es razonable probar algo y hacer algo diferente si un código de retorno indica una falla, mientras que en Dot Net, la captura de excepciones es mucho más lenta que la verificación de una condición válida antes de intentarlo.

Índices Cree índices en los campos de base de datos consultados con frecuencia. Casi siempre puedes intercambiar espacio por velocidad.

Evite búsquedas Dentro del ciclo para ser optimizado, evito tener que hacer búsquedas. Encuentre el desplazamiento y / o índice fuera del bucle y reutilice los datos dentro.

Minimice IO intente diseñar de una manera que reduzca la cantidad de veces que tiene que leer o escribir, especialmente a través de una conexión en red

Reduzca las abstracciones Cuantas más capas de abstracción tenga que procesar el código, más lento será. Dentro del ciclo crítico, reduzca las abstracciones (por ejemplo, revele métodos de nivel inferior que eviten el código adicional)

Generar subprocesos para proyectos con una interfaz de usuario, generar un nuevo subproceso para realizar tareas más lentas hace que la aplicación se sienta más receptiva, aunque no lo es.

Preproceso Generalmente puede intercambiar espacio por velocidad. Si hay cálculos u otras operaciones intensas, vea si puede calcular previamente parte de la información antes de estar en el ciclo crítico.

Andrew Neely
fuente
5

Si tiene muchas matemáticas de punto flotante altamente paralelas, especialmente de precisión simple, intente descargarlo en un procesador de gráficos (si está presente) usando OpenCL o (para chips NVidia) CUDA. Las GPU tienen una inmensa potencia informática de punto flotante en sus sombreadores, que es mucho mayor que la de una CPU.

Demi
fuente
5

Agregando esta respuesta ya que no la vi incluida en todas las demás.

Minimice la conversión implícita entre tipos y signos:

Esto se aplica al menos a C / C ++, incluso si ya crees que estás libre de conversiones, a veces es bueno probar agregar advertencias del compilador en torno a funciones que requieren rendimiento, especialmente cuidado con las conversiones dentro de los bucles.

Específico de GCC: puede probar esto agregando algunos pragmas detallados alrededor de su código,

#ifdef __GNUC__
#  pragma GCC diagnostic push
#  pragma GCC diagnostic error "-Wsign-conversion"
#  pragma GCC diagnostic error "-Wdouble-promotion"
#  pragma GCC diagnostic error "-Wsign-compare"
#  pragma GCC diagnostic error "-Wconversion"
#endif

/* your code */

#ifdef __GNUC__
#  pragma GCC diagnostic pop
#endif

He visto casos en los que puede obtener una aceleración porcentual al reducir las conversiones generadas por advertencias como esta.

En algunos casos, tengo un encabezado con advertencias estrictas que mantengo incluidas para evitar conversiones accidentales, sin embargo, esto es una compensación, ya que puede terminar agregando una gran cantidad de conversiones a conversiones intencionales silenciosas que pueden hacer que el código esté más desordenado por un mínimo ganancias.

revs ideasman42
fuente
Es por eso que me gusta que en OCaml, la conversión entre tipos numéricos debe ser xplicit.
Gaius
Punto justo de @Gaius, pero en muchos casos cambiar de idioma no es una opción realista. Dado que C / C ++ se usa tanto, es útil para poder hacerlos más estrictos, incluso si su compilador es específico.
ideasman42
4

A veces, cambiar el diseño de sus datos puede ayudar. En C, puede cambiar de una matriz o estructuras a una estructura de matrices, o viceversa.

Nosredna
fuente
4

Ajustar el sistema operativo y el marco.

Puede parecer una exageración, pero piense así: los sistemas operativos y los marcos están diseñados para hacer muchas cosas. Su aplicación solo hace cosas muy específicas. Si pudiera hacer que el sistema operativo hiciera exactamente lo que su aplicación necesita y que su aplicación entienda cómo funciona el marco (php, .net, java), podría obtener mucho mejor de su hardware.

Facebook, por ejemplo, cambió algunas cosas a nivel de kernel en Linux, cambió cómo funciona memcached (por ejemplo, escribieron un proxy memcached y usaron udp en lugar de tcp ).

Otro ejemplo de esto es Window2008. Win2K8 tiene una versión donde puede instalar solo el sistema operativo básico necesario para ejecutar aplicaciones X (por ejemplo, aplicaciones web, aplicaciones de servidor). Esto reduce gran parte de la sobrecarga que el sistema operativo tiene en los procesos en ejecución y le brinda un mejor rendimiento.

Por supuesto, siempre debe agregar más hardware como primer paso ...

Nir Levy
fuente
2
Ese sería un enfoque válido después de que todos los otros enfoques fallaran, o si un sistema operativo específico o una característica del Marco fuera responsable de un rendimiento notablemente disminuido, pero el nivel de experiencia y control necesarios para lograrlo puede no estar disponible para cada proyecto.
Andrew Neely