performance - tributarios - Asignación de memoria/desglose Bottleneck?
terminos tributarios en ingles (12)
Aquí es donde el sistema de asignación de memoria de c / c ++ funciona mejor. La estrategia de asignación predeterminada está bien para la mayoría de los casos, pero se puede cambiar para adaptarse a lo que sea necesario. En los sistemas GC no hay mucho que pueda hacer para cambiar las estrategias de asignación. Por supuesto, hay un precio que pagar, y esa es la necesidad de rastrear asignaciones y liberarlas correctamente. C ++ lleva esto más allá y la estrategia de asignación se puede especificar por clase utilizando el nuevo operador:
class AClass
{
public:
void *operator new (size_t size); // this will be called whenever there''s a new AClass
void *operator new [] (size_t size); // this will be called whenever there''s a new AClass []
void operator delete (void *memory); // if you define new, you really need to define delete as well
void operator delete [] (void *memory);define delete as well
};
Muchas de las plantillas STL le permiten definir asignadores personalizados también.
Al igual que con todas las cosas relacionadas con la optimización, primero debe determinar, a través del análisis de tiempo de ejecución, si la asignación de memoria realmente es el cuello de botella antes de escribir sus propios asignadores.
¿Cuánto de un cuello de botella es asignación / desasignación de memoria en programas típicos del mundo real? Las respuestas de cualquier tipo de programa donde el rendimiento generalmente importa son bienvenidas. ¿Son las implementaciones decentes de malloc / free / garbage collection lo suficientemente rápidas como para ser solo un cuello de botella en algunos casos de esquina, o la mayoría de los software críticos para el rendimiento se beneficiarían significativamente al intentar mantener baja la cantidad de asignaciones de memoria o tener un malloc / free / implementación de recolección de basura?
Nota: no estoy hablando de cosas en tiempo real aquí. Por rendimiento crítico, me refiero a cosas donde el rendimiento importa, pero la latencia no necesariamente.
Editar: Aunque menciono malloc, esta pregunta no pretende ser específica de C / C ++.
Asignar y liberar memoria en términos de rendimiento son operaciones relativamente costosas. Las llamadas en los sistemas operativos modernos tienen que llegar hasta el kernel para que el sistema operativo pueda tratar con memoria virtual, paginación / mapeo, protección de ejecución, etc.
Por otro lado, casi todos los lenguajes de programación modernos ocultan estas operaciones detrás de "asignaturas" que funcionan con búferes preasignados.
Este concepto también lo utilizan la mayoría de las aplicaciones que se centran en el rendimiento.
Casi todas las aplicaciones de alto rendimiento ahora tienen que usar subprocesos para explotar el cómputo paralelo. Aquí es donde entra la velocidad de asignación de memoria real al escribir aplicaciones C / C ++.
En una aplicación C o C ++, malloc / new debe tener un bloqueo en el montón global para cada operación. Incluso sin bloqueos de contención están lejos de ser libres y deben evitarse tanto como sea posible.
Java y C # son mejores en esto porque el enhebrado se diseñó desde el principio y los asignadores de memoria funcionan desde grupos de subprocesos. Esto también se puede hacer en C / C ++, pero no es automático.
Casi todos ustedes están fuera de lugar si hablamos del montón de Microsoft. La sincronización se maneja sin esfuerzo, al igual que la fragmentación.
El montón actual de perferrred es el LFH, (HEAP BAJA FRAGMENTACIÓN ), está predeterminado en vista + OS y se puede configurar en XP, a través de gflag, sin problemas.
Es fácil evitar cualquier problema de bloqueo / bloqueo / contención / bus-bandwitth y el lote con el
HEAP_NO_SERIALIZE
opción durante HeapAlloc o HeapCreate. Esto le permitirá crear / usar un montón sin entrar en una espera entrelazada.
Recomendaría crear varios montones, con HeapCreate, y definir una macro, tal vez, mallocx (enum my_heaps_set, size_t);
estaría bien, por supuesto, necesita realloc, libre también para configurarlo como corresponda. Si quieres ser elegante, haz que autodetección free / realloc detecte qué heap maneja por sí mismo evaluando la dirección del puntero, o incluso agregando alguna lógica para permitir que malloc identifique qué montón usar en función de su id. De subproceso y construcción una jerarquía de montones por subproceso y compartimientos / conjuntos compartidos globales.
Los api Heap * son llamados internamente por malloc / new.
Aquí hay un buen artículo sobre algunos problemas de administración de memoria dinámica, con algunas references aún mejores. Para instrumentar y analizar la actividad de montón.
En Java (y potencialmente otros lenguajes con una implementación de GC decente) la asignación de un objeto es muy barata. En SUN JVM solo necesita 10 ciclos de CPU. Un malloc en C / c ++ es mucho más caro, simplemente porque tiene que hacer más trabajo.
Incluso los objetos de asignación incluso en Java son muy baratos, hacerlo para muchos usuarios de una aplicación web en paralelo aún puede generar problemas de rendimiento, ya que se ejecutarán más ejecuciones de Garbage Collector. Por lo tanto, existen los costos indirectos de una asignación en Java causada por la desasignación realizada por el GC. Estos costos son difíciles de cuantificar porque dependen en gran medida de su configuración (la cantidad de memoria que tiene) y su aplicación.
En general, el costo de la asignación de memoria es probablemente menor que la contención de bloqueo, la complejidad algorítmica u otros problemas de rendimiento en la mayoría de las aplicaciones. En general, diría que probablemente no esté en el top-10 de los problemas de rendimiento de los que me preocuparía.
Ahora, tomar grandes cantidades de memoria podría ser un problema. Y agarrarme pero no deshacerme de la memoria es algo de lo que me preocuparía.
En los lenguajes basados en JVM y Java, los objetos nuevos son ahora muy, muy, muy rápidos.
Aquí hay un artículo decente de un tipo que conoce su material con algunas referencias en la parte inferior de más enlaces relacionados: http://www.ibm.com/developerworks/java/library/j-jtp09275.html
En primer lugar, como dijiste malloc, supongo que estás hablando de C o C ++.
La asignación de memoria y la desasignación tienden a ser un importante obstáculo para los programas del mundo real. Mucho pasa "bajo el capó" cuando asigna o desasigna la memoria, y todo es específico del sistema; la memoria puede ser realmente movida o desfragmentada, las páginas pueden reorganizarse, no existe una manera independiente de plataforma para saber cuál será el impacto. Algunos sistemas (como muchas consolas de juegos) tampoco hacen desfragmentación de memoria, por lo que en esos sistemas, comenzará a tener errores de falta de memoria a medida que la memoria se fragmenta.
Una solución típica es asignar tanta memoria por adelantado como sea posible y aferrarse a ella hasta que el programa finalice. Puede usar esa memoria para almacenar grandes conjuntos de datos monolíticos, o usar una implementación de conjunto de memoria para distribuirla en fragmentos. Muchas implementaciones de bibliotecas estándar C / C ++ hacen una cierta cantidad de agrupación de memoria por esta razón.
Sin embargo, no hay dos maneras de hacerlo: si tiene un programa C / C ++ sensible al tiempo, realizar una gran cantidad de asignación / desasignación de memoria matará el rendimiento.
Es significativo, especialmente a medida que crece la fragmentación y el asignador tiene que cazar más fuerte en montones más grandes para las regiones contiguas que solicite. La mayoría de las aplicaciones sensibles al rendimiento generalmente escriben sus propios asignadores de bloque de tamaño fijo (por ejemplo, le piden al sistema operativo para la memoria de 16 MB a la vez y luego parcelar en bloques fijos de 4 kb, 16 kb, etc.) para evitar este problema.
En los juegos, he visto que las llamadas a malloc () / free () consumen hasta un 15% de la CPU (en productos mal escritos), o con asignadores de bloque optimizados y escritos cuidadosamente, tan solo un 5%. Dado que un juego tiene que tener un rendimiento consistente de sesenta hercios, tenerlo parado durante 500ms mientras que un recolector de basura se ejecuta ocasionalmente no es práctico.
Otros han cubierto C / C ++, así que solo agregaré un poco de información sobre .NET.
En .NET, la asignación de heap es generalmente muy rápida, ya que solo se trata de capturar la memoria en la generación cero del montículo. Obviamente, esto no puede continuar para siempre, que es donde entra la recolección de basura. La recolección de basura puede afectar significativamente el rendimiento de su aplicación, ya que los hilos del usuario deben suspenderse durante la compactación de la memoria. Cuantos menos colectas completas, mejor.
Hay varias cosas que puede hacer para afectar la carga de trabajo del recolector de basura en .NET. En general, si tiene mucha memoria, el recolector de basura tendrá que trabajar más. Por ejemplo, implementando un gráfico usando una matriz de adyacencia en lugar de referencias entre nodos, el recolector de basura tendrá que analizar menos referencias.
Si eso es realmente significativo en su aplicación o no depende de varios factores y debe perfilar la aplicación con datos reales antes de recurrir a dichas optimizaciones.
Sé que respondí antes, sin embargo, eso fue en respuesta a las otras respuestas, no a su pregunta.
Para hablar con usted directamente, si entiendo correctamente, su criterio de caso de uso de rendimiento es el rendimiento.
Para mí, esto significa que deberías mirar de forma casi exclusiva a los allocators aware NUMA .
Ninguna de las referencias anteriores; Papel IBM JVM, Microquill C, SUN JVM. Cubra este punto, así que soy muy sospechoso de su aplicación hoy en día, donde, al menos en AMD ABI, NUMA es el preeminente gobernador de la CPU de memoria.
Manos abajo; mundo real, mundo falso, cualquier mundo ... Las tecnologías de solicitud / uso de memoria de NUMA son más rápidas. Desafortunadamente, estoy ejecutando Windows actualmente, y no he encontrado el "numastat" que está disponible en Linux.
Un friend mío ha written sobre esto en profundidad en su implementación para el núcleo de FreeBSD.
A menos que sea capaz de mostrar at-hoc, la cantidad típicamente MUY grande de solicitudes de memoria del nodo local en la parte superior del nodo remoto (subrayando la ventaja obvia del rendimiento del rendimiento ), puede hacer una comparación comparativa, y eso sería lo que necesita todo como su charicterisitc de rendimiento va a ser muy específico.
Lo sé de muchas maneras, al menos 5.x VMWARE se adelantó bastante mal, al menos en ese momento, por no aprovechar NUMA, páginas frecuentemente demandantes del nodo remoto. Sin embargo, las máquinas virtuales son una bestia muy singular cuando se trata de la compartimentalización de la memoria o la contenedorización.
Una de las referencias que cité es la implementación de la API de Microsoft para AMD ABI, que tiene interfaces especializadas de asignación NUMA para que los desarrolladores de aplicaciones terrestres de usuario las exploten;)
Aquí hay un analysis bastante reciente, visual y todo, de algunos desarrolladores de complemento de navegador que comparan 4 implementaciones de montón diferentes. Naturalmente, el que developed resultó ser el mejor (es extraño cómo las personas que hacen la prueba a menudo exhiben los puntajes más altos).
Cubren de cierta manera cuantificable, al menos para su caso de uso, cuál es el intercambio exacto entre espacio / tiempo, generalmente identificaron el LFH (por cierto, LFH es simplemente un modo aparentemente del montón estándar) o un enfoque similarmente diseñado esencialmente consume significativamente más memoria del bate pero con el tiempo puede terminar usando menos memoria ... los grafix también están limpios ...
Sin embargo, creo que seleccionar una implementación de HEAP basada en su carga de trabajo típica una vez que la entiende bien;) es una buena idea, pero para comprender bien sus necesidades, primero asegúrese de que sus operaciones básicas sean correctas antes de optimizar estas probabilidades;
Según las especificaciones técnicas de MicroQuill SmartHeap , "una aplicación típica [...] gasta el 40% de su tiempo de ejecución total en la gestión de la memoria". Puedes tomar esta figura como un límite superior, personalmente siento que una aplicación típica gasta más como 10-15% del tiempo de ejecución asignando / desasignando memoria. Rara vez es un cuello de botella en la aplicación de un solo subproceso.
En las aplicaciones C / C ++ multiproceso, los asignadores estándar se vuelven un problema debido a la contención del bloqueo. Aquí es donde comienzas a buscar soluciones más escalables. Pero ten en cuenta la Ley de Amdahl .
Una máquina virtual de Java reclamará y liberará memoria del sistema operativo de forma bastante independiente de lo que está haciendo el código de la aplicación. Esto le permite tomar y liberar memoria en grandes porciones, lo cual es mucho más eficiente que hacerlo en pequeñas operaciones individuales, como lo hace con la administración manual de la memoria.
Este artículo fue escrito en 2005 y la gestión de memoria al estilo JVM ya estaba en la delantera. La situación solo ha mejorado desde entonces.
¿Qué lenguaje ofrece un rendimiento de asignación de datos bruto más rápido, el lenguaje Java o C / C ++? La respuesta puede sorprenderle: la asignación en las JVM modernas es mucho más rápida que las implementaciones malloc de mejor rendimiento. La ruta del código común para el nuevo objeto () en HotSpot 1.4.2 y posterior es de aproximadamente 10 instrucciones de la máquina (datos proporcionados por Sun; ver Recursos), mientras que las implementaciones de malloc con mejor rendimiento en C requieren un promedio de entre 60 y 100 instrucciones por llamada ( Detlefs, et al., Ver Recursos). Y el rendimiento de la asignación no es un componente trivial del rendimiento general: los puntos de referencia muestran que muchos programas C y C ++ del mundo real, como Perl y Ghostscript, pasan del 20 al 30 por ciento de su tiempo total de ejecución en malloc y gratis; mucho más que la asignación y la sobrecarga de recolección de basura de una aplicación Java sana.