Eliminar correctamente los objetos al finalizar el servidor

9

Estoy trabajando en un gran proyecto de C ++. Consiste en un servidor que expone una API REST, que proporciona una interfaz simple y fácil de usar para un sistema muy amplio que comprende muchos otros servidores. La base de código es bastante grande y compleja, y evolucionó a través del tiempo sin un diseño adecuado por adelantado. Mi tarea es implementar nuevas funciones y refactorizar / corregir el código antiguo para hacerlo más estable y confiable.

Por el momento, el servidor crea una serie de objetos de larga duración que nunca se terminan ni se eliminan cuando finaliza el proceso. Esto hace que Valgrind sea casi inutilizable para la detección de fugas, ya que es imposible distinguir entre los miles de fugas (cuestionables) legítimas de las fugas "peligrosas".

Mi idea es asegurar que todos los objetos se eliminen antes de la terminación, pero cuando hice esta propuesta, mis colegas y mi jefe se opusieron a mí señalando que el sistema operativo va a liberar esa memoria de todos modos (lo cual es obvio para todos) y desechando los objetos ralentizará el apagado del servidor (que, en este momento, es básicamente una llamada a std::exit). Respondí que tener un procedimiento de apagado "limpio" no implica necesariamente que uno deba usarlo. Siempre podemos llamar std::quick_exito simplemente kill -9el proceso si nos sentimos impacientes.

Respondieron "la mayoría de los demonios y procesos de Linux no se molestan en liberar memoria al apagar". Si bien puedo ver eso, también es cierto que nuestro proyecto necesita una depuración precisa de la memoria, ya que encontré corrupción de memoria, liberaciones dobles y variables no inicializadas.

¿Cuáles son tus pensamientos? ¿Estoy persiguiendo un esfuerzo inútil? Si no, ¿cómo puedo convencer a mis colegas y a mi jefe? Si es así, ¿por qué y qué debo hacer en su lugar?

Marty
fuente
Además del argumento de rendimiento (¡lo cual es razonable!), ¿Es mucho esfuerzo aislar los objetos de larga vida y agregarles un código de limpieza?
Doc Brown

Respuestas:

7

Agregue un interruptor al proceso del servidor que se pueda usar durante las mediciones valgrind que liberarán toda la memoria. Puede usar este interruptor para realizar pruebas. El impacto será mínimo durante las operaciones normales.

Tuvimos un proceso de larga ejecución que tardaría varios minutos en liberar miles de objetos. Era mucho más eficiente salir y dejarlos morir. Desafortunadamente, como usted indica, esto dificultó la detección de verdaderas pérdidas de memoria con valgrind o cualquier otra herramienta.

Este fue un buen compromiso para nuestras pruebas sin afectar el rendimiento normal.

Bill Door
fuente
1
+1 Pragmatismo FTW. Hay un valor en la medición, pero también hay un valor en el apagado rápido.
Ross Patterson
2
Como alternativa a un cambio de línea de comando, también puede considerar implementar la eliminación de los objetos permanentes dentro de un bloque #ifdef DEBUG.
Jules
3

La clave aquí es esta:

Si bien puedo ver eso, también es cierto que nuestro proyecto necesita una depuración precisa de la memoria, ya que encontré corrupción de memoria, liberaciones dobles y variables no inicializadas.

Esto implica directamente que su base de código está hecha de nada más que esperanza y cadena. Los programadores competentes de C ++ no tienen liberaciones dobles.

Estás buscando un esfuerzo inútil, como si estuvieras abordando un pequeño síntoma del problema real, que es que tu código es tan confiable como el módulo de servicio Apollo 13.

Si programa su servidor correctamente con RAII, estos problemas no ocurrirán, y el problema en su pregunta será eliminado. Además, su código podría ejecutarse correctamente de vez en cuando. Por lo tanto, es claramente la mejor opción.

DeadMG
fuente
Ciertamente, el problema radica en una imagen mayor. Sin embargo, es muy raro que uno pueda encontrar recursos y permisos para refactorizar / reescribir un proyecto para mejorar su forma.
Cengiz puede
@CengizCan: Si desea corregir errores, necesita refactorizar. Asi es como funciona.
DeadMG
2

Un buen enfoque sería reducir la discusión con sus colegas mediante la clasificación. Dada una gran base de código, ciertamente no hay una sola razón, sino más bien múltiples razones (identificables) para objetos de larga vida.

Ejemplos:

  • Objetos de larga vida a los que nadie hace referencia (fugas reales). Es un error de lógica de programación. Repare aquellos con menor prioridad A MENOS QUE sean responsables de que su huella de memoria crezca con el tiempo (y deteriore la calidad de sus aplicaciones). Si hacen que su huella de memoria crezca con el tiempo, corríjalos con mayor prioridad.

  • Objetos de larga vida, que todavía se mencionan pero ya no se usan (debido a la lógica del programa), pero que no hacen crecer su huella de memoria. Revise el código e intente encontrar los otros errores que conducen a eso. Agregue comentarios a la base del código si se trata de una optimización intencional (rendimiento).

  • Objetos de larga vida "por diseño". Patrón Singleton por ejemplo. De hecho, son difíciles de eliminar, especialmente si se trata de una aplicación multiproceso.

  • Objetos reciclados. Los objetos de larga vida no siempre tienen que ser malos. También pueden ser beneficiosos. En lugar de tener asignaciones / desasignaciones de memoria de alta frecuencia, agregar objetos actualmente no utilizados a un contenedor para extraerlos cuando tal bloque de memoria se necesita nuevamente puede ayudar a acelerar una aplicación y evitar la fragmentación del montón. Deben ser fáciles de liberar en el momento del apagado, tal vez en una compilación especial de "instrumentación / verificación".

  • "objetos compartidos": objetos que son utilizados (referenciados) por varios otros objetos y nadie sabe exactamente cuándo se guarda para liberarlos. Considere convertirlos en objetos contados de referencia.

Una vez que haya clasificado las razones reales de esos objetos no liberados, es mucho más fácil ingresar una discusión caso por caso y encontrar una solución.

BitTickler
fuente
0

En mi humilde opinión, la vida útil de estos objetos nunca debe hacerse y dejarse morir cuando el sistema se apaga. Esto apesta a variables globales, que todos sabemos son malas, malas, malas, malas. Especialmente en la era de los punteros inteligentes, no hay otra razón para hacerlo que la pereza. Pero lo más importante, agrega un nivel de deuda técnica a su sistema con el que alguien tendrá que lidiar algún día.

La idea de "deuda técnica" es que cuando tomas un atajo como este, cuando alguien quiere cambiar el código en el futuro (digamos, quiero poder hacer que el cliente entre en "modo fuera de línea" o "modo de suspensión" ", o quiero poder cambiar de servidor sin reiniciar el proceso) tendrán que esforzarse para hacer lo que se salta. Pero mantendrán su código, por lo que no sabrán tanto sobre él como usted, por lo que les llevará mucho más tiempo (no estoy hablando un 20% más, ¡estoy hablando 20 veces más!). Incluso si es usted, entonces no habrá trabajado en este código en particular durante semanas o meses, y llevará mucho más tiempo desempolvar las telarañas para implementarlo correctamente.

En este caso, parece que tiene un acoplamiento muy estrecho entre el objeto del servidor y los objetos de "larga vida" ... hay momentos en que un registro podría (y debería) sobrevivir a la conexión con el servidor. Mantener cada cambio en un objeto en un servidor puede ser prohibitivamente costoso, por lo que generalmente es mejor que los objetos generados realmente sean chacheados al objeto del servidor, con llamadas de guardar y actualizar que realmente cambian el servidor. Esto se llama comúnmente el patrón de registro activo. Ver:

http://en.wikipedia.org/wiki/Active_record_pattern

En C ++, haría que cada registro activo tuviera un débil_ptr para el servidor, lo que podría arrojar cosas de forma inteligente si la conexión del servidor se apaga. Estas clases se pueden llenar perezosamente o por lotes, dependiendo de sus necesidades, pero la vida útil de esos objetos solo debe estar donde se usan.

Ver también:

¿Es una pérdida de tiempo liberar recursos antes de salir de un proceso?

El otro

IdeaHat
fuente
This reeks of global variables¿Cómo pasar de "hay miles de objetos que necesitan ser liberados" a "deben ser globales"? Eso es un gran salto de lógica.
Doval
0

Si puede identificar fácilmente dónde se asignan los objetos que deberían sobrevivir indefinidamente, una vez sería la posibilidad de asignarlos utilizando un mecanismo de asignación alternativo para que no aparezcan en un informe de fuga de valgrind o simplemente parezcan ser una sola asignación.

En caso de que no esté familiarizado con la idea, aquí hay un artículo sobre cómo impotente asignación de memoria personalizada en c ++ , aunque tenga en cuenta que su solución podría ser más simple que los ejemplos en ese artículo, ya que no necesita manejar la eliminación en absoluto !

Jules
fuente