[c++] RAII contra el recolector de basura


5 Answers

La recolección de basura resuelve ciertas clases de problemas de recursos que RAII no puede resolver. Básicamente, se reduce a dependencias circulares donde no se identifica el ciclo antes de la mano.

Esto le da dos ventajas. En primer lugar, habrá ciertos tipos de problemas que RAII no puede resolver. Estos son, en mi experiencia, raros.

El más grande es que permite que el programador sea flojo y no se preocupe por la vida útil de los recursos de memoria y otros recursos que no le molestan la demora en la limpieza. Cuando no tiene que preocuparse por ciertos tipos de problemas, puede preocuparse más por otros problemas. Esto le permite enfocarse en las partes de su problema en las que desea enfocarse.

La desventaja es que sin RAII, administrar los recursos cuya vida desea restringir es difícil. Los lenguajes de GC básicamente lo reducen a tener tiempos de vida extremadamente simples limitados por el alcance o requieren que haga la administración de recursos manualmente, como en C, al indicar manualmente que ha terminado con un recurso. Su sistema de vida de objetos está fuertemente ligado a GC, y no funciona bien para una gestión estrecha de por vida de sistemas complejos grandes (pero sin ciclos).

Para ser justos, la administración de recursos en C ++ requiere mucho trabajo para funcionar correctamente en sistemas tan grandes (pero sin ciclos). C # y otros idiomas similares lo hacen un poco más difícil, a cambio facilitan la tarea fácil.

La mayoría de las implementaciones de GC también fuerzan clases completas de no localidad; crear búferes contiguos de objetos generales, o componer objetos generales en un objeto más grande, no es algo que la mayoría de las implementaciones de GC faciliten. Por otro lado, C # le permite crear struct tipo de valor con capacidades algo limitadas. En la era actual de la arquitectura de CPU, la compatibilidad con la memoria caché es clave, y la falta de fuerzas GC locales es una carga pesada. Como estos lenguajes tienen un tiempo de ejecución de bytecode en su mayor parte, en teoría el entorno JIT podría mover datos comúnmente usados ​​juntos, pero la mayoría de las veces solo se obtiene una pérdida de rendimiento uniforme debido a fallas frecuentes de caché en comparación con C ++.

El último problema con GC es que la desasignación es indeterminada y, a veces, puede causar problemas de rendimiento. Los GC modernos hacen de este un problema menor de lo que ha sido en el pasado.

Question

Hace poco vi una gran charla de Herb Sutter sobre "Leak Free C ++ ..." en CppCon 2016 donde habló sobre el uso de punteros inteligentes para implementar RAII (adquisición de recursos es inicialización) Conceptos y cómo resuelven la mayoría de los problemas de pérdida de memoria.

Ahora me estaba preguntando. Si sigo estrictamente las reglas de RAII, lo que parece ser algo bueno, ¿por qué sería eso diferente de tener un recolector de basura en C ++? Sé que con RAII el programador tiene el control total de cuándo se liberan los recursos nuevamente, pero ¿es eso en cualquier caso beneficioso para simplemente tener un recolector de basura? ¿Sería realmente menos eficiente? Incluso me enteré de que tener un recolector de basura puede ser más eficiente, ya que puede liberar grandes trozos de memoria a la vez en lugar de liberar pequeñas piezas de memoria en todo el código.




La recolección de basura y la RAII admiten cada una una construcción común para la cual la otra no es realmente adecuada.

En un sistema recolectado de basura, el código puede tratar de manera eficiente las referencias a objetos inmutables (tales como cadenas) como proxies para los datos contenidos en el mismo; pasar tales referencias es casi tan barato como pasar por punteros "tontos", y es más rápido que hacer una copia separada de los datos para cada propietario, o tratar de rastrear la propiedad de una copia compartida de los datos. Además, los sistemas recogidos de basura facilitan la creación de tipos de objetos inmutables al escribir una clase que crea un objeto mutable, rellenarlo como se desee y proporcionar métodos de acceso, todo mientras se evita referencias fugas a cualquier cosa que pueda mutar una vez que el constructor termina En los casos en que las referencias a objetos inmutables necesitan ser ampliamente copiadas, pero los objetos en sí no lo hacen, GC supera a RAII sin problemas.

Por otro lado, RAII es excelente para manejar situaciones en las que un objeto necesita adquirir servicios exclusivos de entidades externas. Mientras que muchos sistemas GC permiten que los objetos definan métodos de "Finalización" y solicitan notificaciones cuando se descubre que se abandonaron, y dichos métodos a veces logran liberar servicios externos que ya no son necesarios, rara vez son lo suficientemente confiables como para proporcionar una forma satisfactoria de garantizar la liberación oportuna de servicios externos. Para la administración de recursos externos no fungibles, RAII supera a GC sin inconvenientes.

La diferencia clave entre los casos en los que GC gana frente a los que gana RAII es que GC es bueno en la administración de memoria fungible que se puede liberar según sea necesario, pero deficiente en el manejo de recursos no fungibles. RAII es bueno en el manejo de objetos con una clara propiedad, pero es malo en el manejo de titulares de datos inmutables sin dueño que no tienen una identidad real aparte de los datos que contienen.

Debido a que ni GC ni RAII manejan bien todos los escenarios, sería útil que los idiomas brinden un buen soporte para ambos. Desafortunadamente, los idiomas que se enfocan en uno tienden a tratar al otro como una ocurrencia tardía.




Incluso me enteré de que tener un recolector de basura puede ser más eficiente, ya que puede liberar grandes trozos de memoria a la vez en lugar de liberar pequeñas piezas de memoria en todo el código.

Eso es perfectamente factible, y, de hecho, ya está hecho, con RAII (o con malloc simple / libre). Verá, no necesariamente siempre usa el asignador predeterminado, que desasigna solo partes. En ciertos contextos, utiliza asignadores personalizados con diferentes tipos de funcionalidad. Algunos asignadores tienen la capacidad incorporada de liberar todo en una región de asignador, todo a la vez, sin tener que iterar elementos asignados individuales.

Por supuesto, entonces se aborda la cuestión de cuándo desasignar todo: si el uso de esos asignadores (o la losa de memoria con la que están asociados debe ser RAIIed o no, y cómo).




Mas o menos. La expresión RAII puede ser mejor para la latencia y la inestabilidad . Un recolector de basura puede ser mejor para el rendimiento del sistema.




RAII y GC resuelven problemas en direcciones completamente diferentes. Son completamente diferentes, a pesar de lo que dirían algunos.

Ambos abordan el problema de que administrar recursos es difícil. Garbage Collection lo resuelve haciendo que el desarrollador no tenga que prestar tanta atención a la administración de esos recursos. RAII lo resuelve haciendo que sea más fácil para los desarrolladores prestar atención a su gestión de recursos. Cualquiera que diga que hace lo mismo tiene algo que venderte.

Si observa las tendencias recientes en idiomas, verá que ambos enfoques se utilizan en el mismo idioma porque, francamente, realmente necesita ambos lados del rompecabezas. Está viendo muchos idiomas que utilizan algún tipo de recolección de basura para no tener que prestar atención a la mayoría de los objetos, y esos lenguajes también ofrecen soluciones RAII (como Python's with operador) para los momentos en los que realmente quiere prestar atención. para ellos

  • C ++ ofrece RAII a través de constructores / destructores y GC a través de shared_ptr (si puedo argumentar que refcounting y GC están en la misma clase de soluciones porque están diseñadas para ayudarlo a no tener que prestar atención a la vida útil)
  • Python ofrece RAII with y GC a través de un sistema de recuento más un recolector de basura
  • C # ofrece RAII a través de IDisposable y el using GC a través de un colector de basura generacional

Los patrones están apareciendo en todos los idiomas.




"Eficiente" es un término muy amplio, en el sentido de los esfuerzos de desarrollo RAII es típicamente menos eficiente que GC, pero en términos de rendimiento, GC es típicamente menos eficiente que RAII. Sin embargo, es posible proporcionar contr-examples para ambos casos. Tratar con GC genérico cuando tiene patrones de asignación de recursos (de) muy claros en lenguajes administrados puede ser bastante problemático, al igual que el código que usa RAII puede ser sorprendentemente ineficiente cuando se usa shared_ptr para todo sin ninguna razón.




Related