sanitarios residuos rellenos recicla que problematica por plasticos los hay hacemos genera donde cuanta cual con ceamse cantidad basura argentina c# garbage-collection reference-counting

c# - residuos - problematica de la basura en la argentina



¿Por qué no hay Recuento de referencias+Recolección de basuras en C#? (10)

El recuento de referencias se intentó en C #. Creo que la gente que lanzó Rotor (una implementación de referencia de CLR para la cual la fuente estuvo disponible) hizo referencia al GC basado en conteo solo para ver cómo se compararía con el generacional. El resultado fue sorprendente: el GC "estándar" era mucho más rápido, ni siquiera gracioso. No recuerdo exactamente dónde escuché esto, creo que fue uno de los podcasts de Hanselmuntes. Si quieres ver a C ++ básicamente aplastado en la comparación de rendimiento con C # - la aplicación del diccionario chino de Google Raymond Chen. Hizo una versión en C ++, y luego Rico Mariani hizo una C #. Creo que le tomó a Raymond 6 iteraciones para finalmente vencer a la versión de C #, pero en ese momento tuvo que descartar todas las orientaciones de objeto agradables de C ++, y llegar al nivel de API win32. Todo se convirtió en un truco de rendimiento. El programa C #, al mismo tiempo, se optimizó solo una vez, y al final parecía un proyecto decente de OO

Vengo de un fondo de C ++ y he estado trabajando con C # durante aproximadamente un año. Al igual que muchos otros, estoy desconcertado sobre por qué el manejo de recursos determinista no está incorporado al lenguaje. En lugar de destructores deterministas, tenemos el patrón de eliminación. La gente comienza a preguntarse si vale la pena divulgar el cáncer identificable a través de su código.

En mi cerebro sesgado en C ++, parece que el uso de punteros inteligentes contados por referencia con destructores deterministas es un gran paso adelante de un recolector de basura que requiere que implemente IDisposable y llame para eliminar los recursos que no son de memoria. Es cierto que no soy muy inteligente ... así que lo estoy pidiendo simplemente por un deseo de comprender mejor por qué las cosas son como son.

¿Qué pasa si C # se modificaron de manera que:

Los objetos son referencias contadas. Cuando el recuento de referencias de un objeto va a cero, un método de limpieza de recursos se llama de manera determinista en el objeto, luego el objeto se marca para la recolección de elementos no utilizados. La recolección de basura se produce en algún momento no determinista en el futuro en el que se recupera la memoria de punto. En este escenario, no tiene que implementar IDisposable ni recordar llamar a Dispose. Simplemente implementa la función de limpieza de recursos si tiene recursos que no son de memoria para liberar.

  • ¿Por qué es eso una mala idea?
  • ¿Eso vencería el propósito del recolector de basura?
  • ¿Sería factible implementar tal cosa?

EDITAR: De los comentarios hasta ahora, esta es una mala idea porque

  1. GC es más rápido sin recuento de referencias
  2. problema de tratar con ciclos en el gráfico de objetos

Creo que el número uno es válido, pero el número dos es fácil de tratar con referencias débiles.

Entonces, la optimización de la velocidad supera los inconvenientes que usted:

  1. no puede liberar un recurso que no sea de memoria de manera oportuna
  2. podría liberar un recurso que no es de memoria demasiado pronto

Si su mecanismo de limpieza de recursos es determinista y está incorporado al lenguaje, puede eliminar esas posibilidades.


El objeto que implementa IDisposable también debe implementar un finalizador llamado por el GC cuando el usuario no hace una llamada explícita a Dispose - see IDisposable.Dispose en MSDN .

El objetivo de IDisposable es que el GC se ejecute en algún momento no determinista e implemente IDisposable porque tiene un recurso valioso y desea liberarlo en un momento determinista.

Entonces su propuesta no cambiaría nada en términos de IDisposable.

Editar:

Lo siento. No leí tu propuesta correctamente. :-(

Wikipedia tiene una explicación simple de las deficiencias de References counted GC


El recolector de basura no requiere que escriba un método de eliminación para cada clase / tipo que defina. Usted solo define uno cuando necesita hacer algo explícito para la limpieza; cuando ha asignado explícitamente recursos nativos. La mayoría de las veces, el GC simplemente recupera memoria incluso si solo hace algo como new () arriba de un objeto.

El GC hace referencia al conteo; sin embargo, lo hace de una manera diferente al encontrar qué objetos son ''alcanzables'' ( Ref Count > 0 ) cada vez que hace una colección ... simplemente no lo hace de manera entera. . Los objetos inalcanzables se recopilan ( Ref Count = 0 ). De esta manera, el tiempo de ejecución no tiene que hacer las tablas de mantenimiento / actualización cada vez que se asigna o libera un objeto ... debe ser más rápido.

La única diferencia importante entre C ++ (determinista) y C # (no determinista) es cuando el objeto se limpiaría. No se puede predecir el momento exacto en que se recolectará un objeto en C #.

Enésimo enchufe: recomendaría leer el capítulo de standup de Jeffrey Richter sobre el GC en CLR a través de C # en caso de que realmente esté interesado en cómo funciona el GC.


Hay muchos problemas en juego aquí. En primer lugar, debe distinguir entre la liberación de memoria administrada y la limpieza de otros recursos. El primero puede ser muy rápido, mientras que el último puede ser muy lento. En .NET, los dos están separados, lo que permite una limpieza más rápida de la memoria administrada. Esto también implica que solo debe implementar Dispose / Finalizer cuando tiene algo más allá de la memoria administrada para limpiar.

.NET emplea una técnica de marca y barrido en la que atraviesa el montón buscando raíces en los objetos. Las instancias rooteadas sobreviven a la recolección de basura. Todo lo demás se puede limpiar simplemente recuperando la memoria. El GC tiene que compactar memoria de vez en cuando, pero aparte de eso, recuperar memoria es una operación de puntero simple incluso cuando se reclaman instancias múltiples. Compare esto con múltiples llamadas a destructores en C ++.


Sé algo sobre la recolección de basura. Aquí hay un breve resumen porque una explicación completa está más allá de los límites de esta pregunta.

.NET utiliza una copiadora y compactadora de basura generacional. Esto es más avanzado que el recuento de referencias y tiene el beneficio de poder recolectar objetos que se refieren a sí mismos ya sea directamente oa través de una cadena.

El recuento de referencias no recogerá ciclos. El recuento de referencias también tiene un rendimiento más bajo (más lento en general) pero con el beneficio de pausas más rápidas (las pausas máximas son más pequeñas) que un colector de rastreo.


Recuento de referencia

Los costos de usar recuentos de referencia son dos: en primer lugar, cada objeto requiere el campo de recuento de referencia especial. Normalmente, esto significa que se debe asignar una palabra adicional de almacenamiento en cada objeto. En segundo lugar, cada vez que se asigna una referencia a otra, se deben ajustar los recuentos de referencia. Esto aumenta significativamente el tiempo empleado por las declaraciones de asignación.

Recolección de basura en .NET

C # no utiliza el recuento de referencias de los objetos. En su lugar, mantiene un gráfico de las referencias de objeto de la pila y navega desde la raíz para cubrir todos los objetos a los que se hace referencia. Todos los objetos referenciados en el gráfico se compactan en el montón para que una memoria contigua esté disponible para objetos futuros. Se recupera la memoria para todos los objetos sin referencia que no necesitan ser finalizados. Aquellos que no tienen referencias pero tienen finalizadores que se ejecutarán en ellos se mueven a una cola separada llamada la cola f-reach donde el recolector de basura llama a sus finalizadores en segundo plano.

Además de lo anterior, GC utiliza el concepto de generaciones para una recolección de basura más eficiente. Se basa en los siguientes conceptos 1. Es más rápido compactar la memoria para una parte del montón administrado que para todo el montón gestionado 2. Los objetos más nuevos tendrán tiempos de vida más cortos y los objetos más antiguos tendrán vidas más largas 3. Los objetos más nuevos tienden a estar relacionado entre sí y acceder a la aplicación en el mismo momento

El montón gestionado se divide en tres generaciones: 0, 1 y 2. Los nuevos objetos se almacenan en gen 0. Los objetos que no son recuperados por un ciclo de GC se promueven a la próxima generación. Por lo tanto, si los objetos más nuevos que están en gen 0 sobreviven al ciclo 1 de GC, entonces se promueven a gen 1. Aquellos entre ellos que sobreviven al ciclo 2 de GC se promueven a gen 2. Como el recolector de basura solo admite tres generaciones, los objetos de generación 2 sobreviven una colección permanecen en la generación 2 hasta que se determine que no se pueden alcanzar en una colección futura.

El recolector de elementos no utilizados realiza una recopilación cuando la generación 0 está llena y se debe asignar memoria para el nuevo objeto. Si una colección de la generación 0 no recupera suficiente memoria, el recolector de elementos no utilizados puede realizar una colección de la generación 1 y la generación 0. Si esto no recupera suficiente memoria, el recolector de elementos no utilizados puede realizar una colección de generaciones 2, 1 y 0 .

Por lo tanto, GC es más eficiente que el recuento de referencia.


Brad Abrams publicó un correo electrónico de Brian Harry escrito durante el desarrollo del .Net framework. Detalla muchas de las razones por las que no se utilizó el recuento de referencias, incluso cuando una de las primeras prioridades era mantener la equivalencia semántica con VB6, que utiliza el recuento de referencias. IRefCounted posibilidades, como tener algunos tipos contados y no otros (¡ IRefCounted !), O que se IRefCounted instancias específicas, y por qué ninguna de estas soluciones se consideró aceptable.

Debido a que [el tema de la gestión de los recursos y la finalización determinista] es un tema tan delicado, trataré de ser tan preciso y completo en mi explicación como pueda. Me disculpo por la longitud del correo. El primer 90% de este correo intenta convencerlo de que el problema es realmente difícil. En esa última parte, hablaré sobre cosas que estamos tratando de hacer pero necesita la primera parte para entender por qué estamos viendo estas opciones.

...

Inicialmente comenzamos con la suposición de que la solución tomaría la forma de conteo automático de ref (para que el programador no lo olvidara) más algunas otras cosas para detectar y manejar ciclos automáticamente. ... finalmente llegamos a la conclusión de que esto no iba a funcionar en el caso general.

...

En resumen:

  • Creemos que es muy importante resolver el problema del ciclo sin obligar a los programadores a comprender, rastrear y diseñar estos complejos problemas de estructura de datos.
  • Queremos asegurarnos de tener un sistema de alto rendimiento (tanto de velocidad como de trabajo) y nuestro análisis muestra que el uso del recuento de referencias para cada objeto del sistema no nos permitirá alcanzar este objetivo .
  • Por una variedad de razones, incluidos los problemas de composición y fundición, no existe una solución simple y transparente para tener solo los objetos que necesitan ser refutados .
  • Optamos por no seleccionar una solución que proporcione una finalización determinista para un único idioma / contexto porque inhibe la interoperabilidad con otros lenguajes y causa la bifurcación de las bibliotecas de clases al crear versiones específicas del idioma.

La gestión determinística de recursos que no son de memoria es parte del lenguaje, sin embargo, no se hace con destructores.

Su opinión es común entre las personas que provienen de un fondo C ++, tratando de utilizar el patrón de diseño RAII . En C ++, la única manera en que puede garantizar que algún código se ejecutará al final de un ámbito, incluso si se produce una excepción, es asignar un objeto en la pila y colocar el código de limpieza en el destructor.

En otros idiomas (C #, Java, Python, Ruby, Erlang, ...) puede usar try-finally (o try-catch-finally) para asegurarse de que el código de limpieza siempre se ejecutará.

// Initialize some resource. try { // Use the resource. } finally { // Clean-up. // This code will always run, whether there was an exception or not. }

IC #, también puede usar la construcción que usa :

using (Foo foo = new Foo()) { // Do something with foo. } // foo.Dispose() will be called afterwards, even if there // was an exception.

Por lo tanto, para un programador en C ++, podría ser útil pensar en "ejecutar el código de limpieza" y "liberar la memoria" como dos cosas separadas. Ponga su código de limpieza en un bloque final y deje que el GC cuide la memoria.


Vengo de un fondo de C ++ y he estado trabajando con C # durante aproximadamente un año. Al igual que muchos otros, estoy desconcertado sobre por qué el manejo de recursos determinista no está incorporado al lenguaje.

La construcción que using proporciona una administración de recursos "determinista" y está integrada en el lenguaje C #. Tenga en cuenta que por "determinista" me refiero a Dispose se garantiza que se ha llamado antes que el código después de que el bloque de using comience a ejecutarse. Tenga en cuenta también que esto no es lo que significa la palabra "determinista", pero todos parecen abusar de él en este contexto de esa manera, lo que apesta.

En mi cerebro sesgado en C ++, parece que el uso de punteros inteligentes contados por referencia con destructores deterministas es un gran paso adelante de un recolector de basura que requiere que implemente IDisposable y llame para eliminar los recursos que no son de memoria.

El recolector de basura no requiere que implemente IDisposable . De hecho, el GC es completamente ajeno a eso.

Es cierto que no soy muy inteligente ... así que lo estoy pidiendo simplemente por un deseo de comprender mejor por qué las cosas son como son.

La recolección de basura de rastreo es una forma rápida y confiable de emular una máquina de memoria infinita, liberando al programador de la carga de la administración manual de la memoria. Esto eliminó varias clases de errores (punteros colgantes, libre demasiado pronto, doble gratis, se olvidó de liberar).

¿Qué pasa si C # se modificaron de manera que:

Los objetos son referencias contadas. Cuando el recuento de referencia de un objeto llega a cero, un método de limpieza de recursos se llama determinísticamente en el objeto,

Considere un objeto compartido entre dos hilos. Los hilos corren para disminuir la cuenta de referencia a cero. Un hilo ganará la carrera y el otro será responsable de la limpieza. Eso no es determinista. La creencia de que el conteo de referencias es intrínsecamente determinista es un mito.

Otro mito común es que el conteo de referencias libera objetos en el punto más temprano posible en el programa. No es así Los decrementos siempre se difieren, generalmente hasta el final del alcance. Esto mantiene los objetos vivos por más tiempo de lo necesario, dejando lo que se llama "basura flotante". Tenga en cuenta que, en particular, algunos recolectores de basura de seguimiento pueden reciclar objetos y lo hacen antes que las implementaciones de conteo de referencias basadas en el alcance.

entonces el objeto está marcado para la recolección de basura. La recolección de basura se produce en algún momento no determinista en el futuro en el que se recupera la memoria de punto. En este escenario, no tiene que implementar IDisposable ni recordar llamar a Dispose.

No tiene que implementar IDisposable para objetos recolectados basura de todos modos, por lo que no es beneficioso.

Simplemente implementa la función de limpieza de recursos si tiene recursos que no son de memoria para liberar.

¿Por qué es eso una mala idea?

El conteo de referencias ingenuo es muy lento y pierde ciclos. Por ejemplo, el shared_ptr de Boost en C ++ es hasta 10 veces más lento que el GC de seguimiento de OCaml . Incluso el conteo de referencias basado en el alcance ingenuo no es determinista en la presencia de programas multiproceso (que es casi todos los programas modernos).

¿Eso vencería el propósito del recolector de basura?

No, en absoluto, no. De hecho, es una mala idea que se inventó en la década de 1960 y se sometió a un intenso estudio académico durante los siguientes 54 años, llegando a la conclusión de que el recuento de referencias apesta en el caso general.

¿Sería factible implementar tal cosa?

Absolutamente. El prototipo inicial .NET y JVM utilizaban el recuento de referencias. También lo encontraron apestado y lo dejaron caer a favor de rastrear GC.

EDITAR: De los comentarios hasta ahora, esta es una mala idea porque

GC es más rápido sin recuento de referencias

Sí. Tenga en cuenta que puede hacer que el recuento de referencias sea mucho más rápido posponiendo los incrementos y decrementos del contador, pero eso sacrifica el determinismo que anhela tanto y es aún más lento que el trazado de GC con los tamaños de pila actuales. Sin embargo, el recuento de referencias es asintóticamente más rápido, por lo que en algún momento en el futuro cuando los montones sean realmente grandes, tal vez comencemos a usar RC en soluciones de administración de memoria automatizada de producción.

problema de tratar con ciclos en el gráfico de objetos

La eliminación de prueba es un algoritmo diseñado específicamente para detectar y recopilar ciclos en sistemas contados de referencia. Sin embargo, es lento y no determinista.

Creo que el número uno es válido, pero el número dos es fácil de tratar con referencias débiles.

Llamar a las referencias débiles "fácil" es un triunfo de la esperanza sobre la realidad. Ellos son una pesadilla. No solo son impredecibles y difíciles de diseñar, sino que contaminan las API.

Entonces, la optimización de la velocidad supera los inconvenientes que usted:

no puede liberar un recurso que no sea de memoria de manera oportuna

¿No using recursos gratuitos sin memoria de manera oportuna?

podría liberar un recurso que no es de memoria demasiado pronto. Si su mecanismo de limpieza de recursos es determinista y está integrado en el lenguaje, puede eliminar esas posibilidades.

La construcción en using es determinista y está integrada en el lenguaje.

Creo que la pregunta que realmente quieres hacer es por qué el recuento de referencias de uso no IDisposable . Mi respuesta es anecdótica: llevo 18 años utilizando idiomas recogidos de basura y nunca tuve que recurrir al recuento de referencias. En consecuencia, prefiero las API más simples que no están contaminadas con complejidad incidental como referencias débiles.


Existe una diferencia entre el recuento de referencias de puntero inteligente de estilo C ++ y la recolección de basura de recuento de referencia. También he hablado sobre las diferencias en mi blog, pero aquí hay un resumen rápido:

Recuento de referencias de estilo C ++:

  • Costo ilimitado al disminuir: si la raíz de una gran estructura de datos se reduce a cero, existe un costo ilimitado para liberar todos los datos.

  • Recolección manual de ciclos: para evitar que las estructuras de datos cíclicas pierdan memoria, el programador debe romper manualmente cualquier estructura potencial reemplazando parte del ciclo con un puntero inteligente débil. Esta es otra fuente de defectos potenciales.

Recuento de referencia Recopilación de basura

  • RC diferido: los cambios en un recuento de referencia de objeto se ignoran para las referencias de pila y registro. En cambio, cuando se activa un GC, estos objetos se conservan al recopilar un conjunto de raíz. Los cambios en el recuento de referencias pueden diferirse y procesarse en lotes. Esto resulta en un mayor rendimiento .

  • Coalescencia: utilizando una barrera de escritura es posible unir los cambios al recuento de referencia. Esto hace que sea posible ignorar la mayoría de los cambios en un recuento de referencias de objetos mejorando el rendimiento de RC para referencias mutadas con frecuencia.

  • Detección de ciclo: para una implementación completa de GC, también se debe usar un detector de ciclo. Sin embargo, es posible realizar la detección de ciclo en forma incremental, lo que a su vez significa tiempo de GC limitado.

Básicamente, es posible implementar un recolector de basura basado en RC de alto rendimiento para tiempos de ejecución como la JVM de Java y el tiempo de ejecución de .NET CLR.

Creo que los colectores de búsqueda se usan parcialmente por razones históricas: muchas de las mejoras recientes en el recuento de referencias se produjeron después de que se publicaron tanto el tiempo de ejecución de JVM como el de .net. El trabajo de investigación también toma tiempo para la transición a proyectos de producción.

Eliminación determinística de recursos

Este es un problema bastante diferente. El tiempo de ejecución de .NET hace esto posible usando la interfaz IDisposable, ejemplo a continuación. También me gusta la respuesta de Gishu .

@Skrymsli , este es el propósito de la palabra clave " usar ". P.ej:

public abstract class BaseCriticalResource : IDiposable { ~ BaseCriticalResource () { Dispose(false); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); // No need to call finalizer now } protected virtual void Dispose(bool disposing) { } }

Luego, para agregar una clase con un recurso crítico:

public class ComFileCritical : BaseCriticalResource { private IntPtr nativeResource; protected override Dispose(bool disposing) { // free native resources if there are any. if (nativeResource != IntPtr.Zero) { ComCallToFreeUnmangedPointer(nativeResource); nativeResource = IntPtr.Zero; } } }

Entonces, usarlo es tan simple como:

using (ComFileCritical fileResource = new ComFileCritical()) { // Some actions on fileResource } // fileResource''s critical resources freed at this point

Ver también implementar IDisposable correctamente .