2012-05-17 21 views
9

Estoy trabajando en una aplicación de servidor grande escrita usando C++. Este servidor necesita ejecutarse posiblemente durante meses sin reiniciarse. La fragmentación ya es un tema sospechoso, ya que nuestro consumo de memoria aumenta con el tiempo. Hasta ahora, la medida ha sido comparar bytes privados con bytes virtuales y analizar la diferencia en esos dos números.Pensando en la fragmentación de la memoria mientras codifica: Optimización prematura o no?

Mi enfoque general a la fragmentación es dejarlo en el análisis. Tengo la misma forma de pensar sobre otras cosas como el rendimiento general y las optimizaciones de la memoria. Tienes que respaldar los cambios con análisis y pruebas.

Estoy notando mucho durante las revisiones de código o discusiones, que la fragmentación de la memoria es una de las primeras cosas que aparece. Es casi como si ahora tuvieran un gran temor, y hay una gran iniciativa para "prevenir la fragmentación" antes de tiempo. Se solicitan cambios de código que parezcan favorables para reducir o prevenir problemas de fragmentación de memoria. Tiendo a estar en desacuerdo con estos desde el principio, ya que me parecen una optimización prematura. Me gustaría sacrificar la limpieza/legibilidad del código/mantenibilidad/etc. para satisfacer estos cambios

Por ejemplo, tomemos el siguiente código:

std::stringstream s; 
s << "This" << "Is" << "a" << "string"; 

Arriba, el número de asignaciones de la stringstream hace que aquí no está definido, podría ser de 4 o asignaciones, sólo 1 de asignación. Por lo tanto, no podemos optimizar solo en función de eso, pero el consenso general es usar un buffer fijo o modificar el código de alguna manera para utilizar potencialmente menos asignaciones. Realmente no veo que el stringstream se expanda aquí como un gran contribuyente a los problemas de memoria, pero tal vez estoy equivocado.

sugerencias de mejora general en el código anterior son a lo largo de las líneas de:

std::stringstream s; 
s << "This is a string"; // Combine it all to 1 line, supposedly less allocations? 

También hay un enorme empuje a utilizar la pila sobre el montón siempre que sea posible.

¿Es posible ser preventivo sobre la fragmentación de la memoria de esta manera, o es simplemente una falsa sensación de seguridad?

+4

Creo que una forma simple de juzgar es esta: te preocupan dos cosas en los programas: la corrección de la implementación y la eficiencia de implementación. Todos queremos la más alta en ambas categorías, pero prácticamente hablando es mejor enfocarse en la corrección que en la eficiencia, porque el programa incorrecto más eficiente en el mundo sigue siendo incorrecto, y sigue siendo inútil. Debes enfocarte en ambos de la mejor manera posible; * la optimización prematura simplemente significa enfocarse más en la eficiencia que en la corrección *. Si tiene la capacidad de hacerlo eficiente sin sacrificar la corrección, ¡definitivamente debería hacerlo! – GManNickG

+3

'La fragmentación ya es un problema sospechoso aquí, ya que nuestro consumo de memoria aumenta con el tiempo. Las simples pérdidas de memoria antiguas serían mucho más sospechosas en mi opinión; podría descartarlas primero (y con un corrector de fugas de memoria como valgrind o drmemory también es mucho más fácil que analizar toda la base de códigos para buscar fuentes de fragmentación) – smocking

Respuesta

14

No es la optimización prematura si se sabe de antemano que usted necesita estar bajo la fragmentación y que se ha medido de antemano que la fragmentación es un problema real para usted y se sabe de antemano qué segmentos de su código son pertinente. El rendimiento es un requisito, pero la optimización ciega es mala en cualquier situación.

Sin embargo, el enfoque superior es utilizar un asignador personalizado libre de fragmentación, como grupo de objetos o arena de memoria, que no garantiza la fragmentación. Por ejemplo, en un motor de física, puede usar un espacio de memoria para todas las asignaciones por tilde y vaciarlo al final, que no solo es ridículamente rápido (incluso más rápido que _alloca en VS2010) sino que también es extremadamente eficiente en cuanto a la memoria y baja fragmentación.

+0

Como desarrollador de juegos, los asignadores y grupos personalizados son muy comunes, y generalmente los construimos desde el principio. No hay razón para no hacerlo cuando sabes que los necesitarás. También hace las cosas mucho más fáciles al determinar los presupuestos para varios subsistemas. –

+0

Supongo que el consenso general aquí es que, debido a que creemos que la fragmentación ya es un problema, las asignaciones futuras amplificarán el problema. En ese sentido, sienten que la optimización no se hace "a ciegas", sin embargo, creo que cada caso es específico del contexto y la existencia de problemas de fragmentación puede no ser relevante para futuras asignaciones. ¿Cuáles son tus pensamientos? –

+0

@RobertDailey: Aún así desearía identificar qué asignaciones realmente están causando la fragmentación. No hay ninguna razón para creer que la asignación aleatoria X suponga una diferencia, al igual que si agrega una función aleatoria X y la llama una vez, será irrelevante incluso si ya está vinculado a la CPU. – Puppy

1

Creo que es más que una mejor práctica que una optimización prematura. Si tiene un conjunto de pruebas, puede crear un conjunto de pruebas de memoria para ejecutar y medir la memoria, el rendimiento, etc., en el período nocturno, por ejemplo. Puede leer los informes y corregir algunos errores si es posible.

El problema con pequeñas optimizaciones es cambiar el código por algo diferente pero con la misma lógica de negocio. Como usar un bucle invertido porque es más rápido que regular. su prueba unitaria probablemente lo guíe para optimizar algunos puntos sin efectos secundarios.

6

Es absolutamente razonable considerar la fragmentación de la memoria en el nivel algorítmico. También es razonable asignar objetos pequeños de tamaño fijo en la pila para evitar el costo de una asignación de montón innecesaria y gratuita. Sin embargo, definitivamente definiría la línea en cualquier cosa que haga que el código sea más difícil de depurar, analizar o mantener.

También me preocuparía que haya muchas sugerencias que simplemente están mal. Probablemente la mitad de las cosas que la gente suele decir que deberían hacerse "para evitar la fragmentación de la memoria" probablemente no tengan ningún efecto y una fracción considerable del resto probablemente sea dañina.

Para aplicaciones de servidor más realistas y de larga ejecución en hardware informático moderno típico, la fragmentación de la memoria virtual de espacio de usuario no será un problema con la codificación simple y directa.

+0

Su segundo comentario es muy importante. Lo que causa la fragmentación no siempre es obvio, ni tampoco la solución. –

-3

Un punto más que me gustaría mencionar es: ¿Por qué no pruebas algún tipo de recolector de basura? puede invocarlo después de un cierto umbral o después de un cierto período de tiempo. el recolector de basura recogerá automáticamente la memoria no utilizada después de un cierto umbral.

También con respecto a la fragmentación, trate de asignar algún tipo de almacenamiento para diferentes tipos de objetos y adminístrelos en el código.

es decir, si tiene 5 tipos de objetos (de clase A, B, C, D y E). puede asignar espacio al principio para, por ejemplo, 1000 objetos de cada tipo en el comienzo en decir cachéA, cacheB ... cacheE.

Por lo tanto, evitará muchas llamadas de malloc y nuevas, así como la fragmentación será muy inferior. También el código será legible como antes, ya que solo necesita implementar algo como myAlloc, que asignará desde su caché, caché, etc. ...

1

Preocuparse por la fragmentación de la memoria antes de que realmente la encuentre es claramente una optimización prematura; No lo tomaría demasiado en cuenta en el diseño inicial. Cosas como una buena encapsulación son más importantes (ya que le permitirán cambiar la representación de la memoria más adelante, si es necesario).

Por otro lado, es buen diseño para evitar la asignación innecesaria, y para utilizar variables locales en lugar de asignación dinámica cuando sea posible. No solo por razones de fragmentación, sino también por razones de simplicidad del programa. C++ tiende a preferir la semántica de valores en general, y los programas que usan semántica de valores (copia y asignación) son más naturales que los que usan semántica de referencia (asignación dinámica y punteros de paso).

0

Creo que no deberías resolver el problema de la fragmentación antes de que realmente lo encuentres, pero al mismo tiempo tu software debe diseñarse para permitir una fácil integración de dicha solución para el problema de fragmentación de la memoria. Y dado que la solución es un asignador de memoria personalizado, significa que conectar uno en su código (operador nuevo/eliminar y clases de asignación para sus contenedores) debe hacerse cambiando una línea de código en algún lugar de su archivo config.h, y absolutamente no por pasando por todas las instancias de todos los contenedores y tal. Otro punto para apoyar esto es que el 99% de todo el software complejo actual tiene múltiples subprocesos y la asignación de memoria de diferentes subprocesos conduce a problemas de sincronización y, en ocasiones, a un intercambio falso. Y la respuesta a estos problemas es nuevamente el asignador de memoria personalizado.

Por lo tanto, si su diseño admite asignadores personalizados, no debe aceptar cambios de código que se le venden como "liberadores de fragmentación", no hasta que perfile su aplicación y compruebe que el parche realmente disminuye el número de DTLB o LLC falla al empacar los datos mejor. Si, sin embargo, el diseño no permite el asignador personalizado, entonces esto debería implementarse como un primer paso antes de hacer cualquier otro cambio de código de "eliminación de fragmentación de memoria".

Por lo que recuerdo sobre el diseño interno, el asignador escalable de Threading Building Blocks podría intentarse con ambos: aumentar la escalabilidad de asignación de memoria y disminuir la fragmentación de memoria.

Otro pequeño punto: el ejemplo que está realizando con las asignaciones de cadenas de caracteres y la política para agrupar las asignaciones tanto como sea posible - entiendo que en algunos casos esto llevará a fragmentación de memoria en lugar de resolver este problema. Al empaquetar todas las asignaciones juntas, se solicitará que solicite grandes porciones contiguas de memoria, que podrían terminar siendo dispersas y, luego, otras solicitudes similares de fragmentos grandes no podrán llenar los espacios.

Cuestiones relacionadas