¿Por qué recolectar basura si hay punteros inteligentes?

62

En estos días, muchos idiomas se recogen basura. Incluso está disponible para C ++ por parte de terceros. Pero C ++ tiene RAII y punteros inteligentes. Entonces, ¿cuál es el punto de usar la recolección de basura? ¿Está haciendo algo extra?

Y en otros lenguajes como C #, si todas las referencias se tratan como punteros inteligentes (manteniendo RAII a un lado), por especificación y por implementación, ¿todavía habrá necesidad de recolectores de basura? Si no, entonces ¿por qué no es así?

    
pregunta Gulshan 27.12.2010 - 12:58

10 respuestas

67
  

Entonces, ¿qué sentido tiene usar la recolección de basura?

Supongo que te refieres a los punteros inteligentes de referencia contados y señalaré que son una forma (rudimentaria) de recolección de basura, así que responderé a la pregunta "¿cuáles son las ventajas de otras formas de recolección de basura sobre la referencia contada? punteros inteligentes "en su lugar.

  • Precisión . El conteo de referencias solo filtra los ciclos, por lo que los punteros inteligentes contabilizados de referencia perderán memoria en general, a menos que se agreguen otras técnicas a los ciclos de captura. Una vez que se agregan esas técnicas, el beneficio de simplicidad del conteo de referencias ha desaparecido. Además, tenga en cuenta que los GC de conteo y seguimiento de referencia basados en el alcance recopilan valores en diferentes momentos, a veces el conteo de referencia se recopila antes y, a veces, el seguimiento de los GC se recopila antes.

  • Rendimiento . Los punteros inteligentes son una de las formas menos eficientes de recolección de basura, particularmente en el contexto de aplicaciones de subprocesos múltiples cuando los recuentos de referencias se realizan de forma atómica. Existen técnicas avanzadas de conteo de referencias diseñadas para aliviar esto, pero el seguimiento de los GC sigue siendo el algoritmo elegido en los entornos de producción.

  • Latencia . Las implementaciones típicas de punteros inteligentes permiten a los destructores hacer una avalancha, lo que resulta en tiempos de pausa ilimitados. Otras formas de recolección de basura son mucho más incrementales e incluso pueden ser en tiempo real, por ejemplo. Cinta de correr del panadero.

respondido por el Jon Harrop 27.12.2010 - 15:48
60

Ya que nadie lo ha visto desde este ángulo, volveré a formular la pregunta: ¿por qué poner algo en el lenguaje si puedes hacerlo en una biblioteca? Ignorando la implementación específica y los detalles sintácticos. GC / smart pointers es básicamente un caso especial de esa pregunta. ¿Por qué definir un recolector de basura en el propio idioma si puede implementarlo en una biblioteca?

Hay un par de respuestas a esa pregunta. Lo más importante primero:

  1. Usted se asegura de que todo el código pueda usarlo para interoperar. Creo que esta es la gran razón por la que la reutilización del código y el uso compartido de código no despegar hasta que Java / C # / Python / Ruby. Las bibliotecas deben comunicarse, y el único idioma compartido confiable que tienen es el contenido de la especificación del idioma (y, en cierta medida, su biblioteca estándar). Si alguna vez ha intentado reutilizar las bibliotecas en C ++, es probable que haya experimentado el dolor terrible que no causa la semántica de la memoria estándar. Quiero pasar una estructura a alguna lib. ¿Paso una referencia? ¿Puntero? %código%? %código%? ¿Estoy pasando la propiedad, o no? ¿Hay alguna manera de indicar eso? ¿Qué pasa si la biblioteca necesita asignar? ¿Tengo que darle un repartidor? Al no hacer que la administración de la memoria sea parte del lenguaje, C ++ obliga a cada par de bibliotecas a tener que negociar su propia estrategia específica aquí, y es muy difícil que todas estén de acuerdo. GC hace que eso no sea un problema completo.

  2. Puede diseñar la sintaxis a su alrededor. Como C ++ no encapsula la administración de la memoria en sí misma, tiene que proporcionar una gama de enlaces sintácticos para permitir que el código de nivel de usuario exprese todo los detalles. Tiene punteros, referencias, scoped_ptr , operadores de eliminación de referencias, operadores de direccionamiento indirecto, direcciones de correo electrónico, etc. Si implementa la administración de memoria en el propio idioma, la sintaxis se puede diseñar para eso. Todos esos operadores desaparecen y el lenguaje se vuelve más limpio y sencillo.

  3. Obtienes un alto retorno de la inversión. El valor que genera cualquier pieza de código determinada se multiplica por la cantidad de personas que lo usan. Esto significa que cuantos más usuarios tenga, más podrá gastar en un software. Cuando mueva una función al idioma, todos los usarán los usuarios del idioma. Esto significa que puede asignarle más esfuerzo que a una biblioteca que solo utiliza un subconjunto de esos usuarios. Esta es la razón por la que lenguajes como Java y C # tienen máquinas virtuales de primera calidad y recolectores de basura de una calidad fantástica: el costo de su desarrollo se amortiza entre millones de usuarios.

respondido por el munificent 27.12.2010 - 23:06
29

Recolección de basura básicamente significa que los objetos asignados se liberan automáticamente cuando ya no se hace referencia a ellos.

Más precisamente, se liberan cuando se vuelven inalcanzables para el programa, ya que los objetos de referencia circular nunca se liberarían de otra manera.

Los punteros inteligentes solo se refieren a cualquier estructura que se comporte como un puntero normal pero tiene alguna funcionalidad adicional adjunta. Estos incluyen pero no se limitan a la dislocación, sino también a la copia en escritura, cheques encuadernados, ...

Ahora, como ha indicado, los punteros inteligentes se pueden usar para implementar una forma de recolección de basura.

Pero la línea de pensamiento sigue el siguiente camino:

  1. La recolección de basura es algo genial, ya que es conveniente y tengo que ocuparme de menos cosas
  2. Por lo tanto: quiero recolección de basura en mi idioma
  3. Ahora, ¿cómo puedo obtener GC en mi idioma?

Por supuesto, puedes diseñarlo así desde el principio. C # fue diseñado para ser recolectado como basura, por lo que solo new su objeto y se liberará cuando las referencias queden fuera del alcance. Cómo se hace esto depende del compilador.

Pero en C ++, no se pretendía la recolección de basura. Si asignamos algún puntero int* p = new int; y queda fuera del alcance, p se elimina de la pila, pero nadie se ocupa de la memoria asignada.

Ahora, lo único que tienes desde el principio son destructores deterministas . Cuando un objeto deja el alcance en el que se ha creado, se llama a su destructor. En combinación con las plantillas y la sobrecarga de operadores, puede diseñar un objeto envoltorio que se comporte como un puntero, pero utiliza la funcionalidad del destructor para limpiar los recursos adjuntos (RAII). Usted llama a este un puntero inteligente .

Todo esto es muy específico de C ++: sobrecarga del operador, plantillas, destructores, ... En esta situación particular del lenguaje, ha desarrollado punteros inteligentes para proporcionarle el GC que desea.

Pero si diseñas un lenguaje con GC desde el principio, esto es simplemente un detalle de implementación. Solo dices que el objeto se limpiará y el compilador hará esto por ti.

Los punteros inteligentes como en C ++ probablemente no serían posibles en lenguajes como C # 's, que no tienen ninguna destrucción determinista (C # soluciona esto al proporcionar azúcar sintáctica para llamar a .Dispose() en ciertos objetos). Los recursos no referenciados finalmente serán reclamados por el GC, pero no estarán definidos cuando sucederá exactamente esto.

Y esto, a su vez, puede permitir que el GC haga su trabajo de manera más eficiente. Al estar integrado más profundamente en el lenguaje que los punteros inteligentes, que se establecen encima de él, el GC .NET puede, por ejemplo. retrasar las operaciones de la memoria y realizarlas en bloques para abaratarlas o incluso mover la memoria para aumentar la eficiencia en función de la frecuencia con la que se accede a los objetos.

    
respondido por el Dario 27.12.2010 - 13:24
4

En mi opinión, hay dos grandes diferencias entre la recolección de basura y los punteros inteligentes que se utilizan para la gestión de la memoria:

  1. Los punteros inteligentes no pueden recoger basura cíclica; bote de basura
  2. Los punteros inteligentes hacen todo el trabajo en los momentos de referencia, eliminación de referencias y desasignación, en el hilo de la aplicación; recolección de basura no necesita

Lo primero significa que GC recolectará basura que los punteros inteligentes no; Si está utilizando punteros inteligentes, debe evitar crear este tipo de basura o estar preparado para lidiar con ella manualmente.

Esto último significa que no importa cuán inteligentes sean los punteros inteligentes, su funcionamiento ralentizará los subprocesos de trabajo en su programa. La recolección de basura puede diferir el trabajo y moverlo a otros subprocesos; eso permite que sea más eficiente en general (de hecho, el costo de tiempo de ejecución de un GC moderno es menor que un sistema malloc / libre normal, incluso sin la sobrecarga adicional de los punteros inteligentes), y hacer el trabajo que aún debe hacer sin entrar en el forma de los hilos de aplicación.

Ahora, tenga en cuenta que los punteros inteligentes, al ser construcciones programáticas, pueden usarse para hacer todo tipo de otras cosas interesantes (vea la respuesta de Darío) que están completamente fuera del alcance de la recolección de basura. Si quieres hacer eso, necesitarás punteros inteligentes.

Sin embargo, a los efectos de la administración de memoria, no veo ninguna posibilidad de que los punteros inteligentes reemplacen la recolección de basura. Simplemente no son tan buenos en eso.

    
respondido por el Tom Anderson 27.12.2010 - 13:51
3

El término recolección de basura implica que hay cualquier basura que recopilar. En C ++, los punteros inteligentes vienen en varios sabores, lo más importante es que unique_ptr. El unique_ptr es básicamente una construcción de propiedad y alcance única. En un código bien diseñado, la mayoría de las cosas asignadas a un montón normalmente residirían detrás de los punteros inteligentes unique_ptr y la propiedad de esos recursos estará bien definida en todo momento. Casi no hay sobrecarga en unique_ptr y unique_ptr elimina la mayoría de los problemas de administración de memoria manual que tradicionalmente llevaban a las personas a idiomas administrados. Ahora que más núcleos que se ejecutan simultáneamente se están volviendo más comunes, los principios de diseño que impulsan el código para usar una propiedad única y bien definida en cualquier momento en el tiempo se vuelven más importantes para el rendimiento. El uso del modelo de cómputo de actor permite la construcción de programas con una cantidad mínima de estado compartido entre subprocesos, y la propiedad única juega un papel importante al hacer que los sistemas de alto rendimiento hagan un uso eficiente de muchos núcleos sin la sobrecarga de compartir entre Hilos de datos y los requisitos de exclusión implícita.

Incluso en un programa bien diseñado, especialmente en entornos de múltiples hilos, no todo puede expresarse sin estructuras de datos compartidas, y para aquellas estructuras de datos que realmente requieren, los hilos deben comunicarse. RAII en c ++ funciona bastante bien para las preocupaciones de la vida útil en una configuración de un solo subproceso, en una configuración de múltiples subprocesos la vida útil de los objetos puede no estar completamente apilada jerárquicamente. Para estas situaciones, el uso de shared_ptr ofrece una gran parte de la solución. Usted crea la propiedad compartida de un recurso y este en C ++ es el único lugar donde vemos basura, pero en cantidades tan pequeñas que un programa de c ++ diseñado apropiadamente debería considerarse más para implementar la recolección de "basura" con ptr compartido que la recolección de basura completa como implementado en otros idiomas. C ++ simplemente no tiene tanta 'basura' para recolectar.

Como han dicho otros, los punteros inteligentes de referencia contados son una forma de recolección de basura, y para eso tiene un problema importante. El ejemplo que se usa principalmente como inconveniente de las formas de recolección de basura contabilizadas de referencia es el problema con la creación de estructuras de datos huérfanas conectadas entre sí con punteros inteligentes que crean grupos de objetos que evitan que se recojan entre sí. Mientras que en un programa diseñado de acuerdo con el modelo de cómputo de actor, las estructuras de datos no suelen permitir que surjan agrupaciones no recopilables en C ++, cuando se usa el enfoque de datos compartidos amplios para la programación de múltiples subprocesos, ya que se usa predominantemente en gran parte De la industria, estos grupos huérfanos pueden convertirse rápidamente en una realidad.

Entonces, para resumir, si por uso de puntero compartido se entiende el uso generalizado de unique_ptr combinado con el modelo de cómputo de actor para la programación de múltiples subprocesos y el uso limitado de shared_ptr, que otras formas de recolección de basura no comprarle cualquier beneficio adicional. Sin embargo, si un enfoque de todo compartido le permitiera terminar con shared_ptr en todo el lugar, entonces debería considerar cambiar los modelos de concurrencia o cambiar a un lenguaje administrado que esté más orientado a la distribución más amplia de la propiedad y el acceso simultáneo a las estructuras de datos.

    
respondido por el user1703394 26.01.2016 - 11:29
2

La mayoría de los punteros inteligentes se implementan mediante el recuento de referencias. Es decir, cada puntero inteligente que se refiere a un objeto incrementa el conteo de referencia de los objetos. Cuando ese conteo llega a cero, el objeto se libera.

El problema existe si tienes referencias circulares. Es decir, A tiene una referencia a B, B tiene una referencia a C y C tiene una referencia a A. Si está utilizando punteros inteligentes, para liberar la memoria asociada con A, B y amp; C, debe ingresar manualmente una "ruptura" de la referencia circular (por ejemplo, utilizando weak_ptr en C ++).

La recolección de basura (normalmente) funciona de manera muy diferente. La mayoría de los recolectores de basura en estos días usan una prueba de accesibilidad . Es decir, examina todas las referencias en la pila y las que son accesibles globalmente y luego rastrea todos los objetos a los que se refieren esas referencias, y los objetos que se refieren a, etc. Todo lo demás es basura .

De esa manera, las referencias circulares ya no importan, siempre que ni A, B ni C sean accesibles , la memoria puede ser reclamada.

Hay otras ventajas en la recolección de basura "real". Por ejemplo, la asignación de memoria es extremadamente barata: simplemente incremente el puntero al "final" del bloque de memoria. La desasignación también tiene un costo amortizado constante. Pero, por supuesto, los lenguajes como C ++ le permiten implementar la administración de la memoria de la manera que más le guste, por lo que podría idear una estrategia de asignación que sea aún más rápida.

Por supuesto, en C ++, la cantidad de memoria asignada al montón es típicamente menor que un lenguaje de referencia como C # / .NET. Pero eso no es realmente un problema de recolección de basura vs. punteros inteligentes.

En cualquier caso, el problema no es cortar y secar uno es mejor que el otro. Cada uno tiene ventajas y desventajas.

    
respondido por el Dean Harding 27.12.2010 - 13:45
2

Se trata de rendimiento . Desasignar memoria requiere mucha administración. Si la no asignación se ejecuta en segundo plano, el rendimiento del proceso en primer plano aumenta. Desafortunadamente, la asignación de memoria no puede ser perezosa (los objetos asignados se utilizarán en el momento santo), pero la liberación de objetos sí puede.

Intente en C ++ (sin ningún GC) para asignar una gran cantidad de objetos, imprima "hola" y luego bórrelos. Te sorprenderá cuánto tiempo se tarda en liberar objetos.

Además, GNU libc proporciona herramientas más efectivas para desasignar memoria, consulte obstacks . Debo tener en cuenta que no tengo experiencia con obstáculos, nunca los usé.

    
respondido por el ern0 27.12.2010 - 15:11
2

La recolección de basura puede ser más eficiente: básicamente 'agrupa' la sobrecarga de la administración de memoria y lo hace todo al mismo tiempo. En general, esto dará como resultado que se gaste menos CPU en general en la desasignación de memoria, pero significa que en algún momento tendrá una gran ráfaga de actividad de desasignación. Si el GC no está diseñado correctamente, el usuario puede verlo como una "pausa" mientras el GC intenta desasignar la memoria. La mayoría de los GC modernos son muy buenos para mantener esto invisible para el usuario, excepto en las condiciones más adversas.

Los punteros inteligentes (o cualquier esquema de conteo de referencia) tienen la ventaja de que suceden exactamente cuando se espera que veamos el código (el puntero inteligente queda fuera del alcance, la cosa se elimina). Tienes pequeñas ráfagas de desasignación aquí y allá. En general, puede usar más tiempo de CPU en la desasignación, pero dado que está distribuido en todas las cosas que suceden en su programa, es menos probable que (descontando la desasignación de alguna estructura de datos de monstruos) sea visible para su usuario.

Si está haciendo algo donde la capacidad de respuesta es importante, sugeriría que el conteo de punteros / referencias inteligentes le permita saber exactamente cuándo están sucediendo las cosas, para que pueda saber mientras codifica lo que es probable que se vuelva visible para sus usuarios. En una configuración de GC, solo tienes el control más efímero sobre el recolector de basura y simplemente tienes que intentar solucionarlo.

Por otra parte, si su meta es el rendimiento general, un sistema basado en GC puede ser una opción mucho mejor, ya que minimiza los recursos necesarios para administrar la memoria.

Ciclos: No considero que el problema de los ciclos sea significativo. En un sistema en el que tiene punteros inteligentes, tiende hacia estructuras de datos que no tienen ciclos, o simplemente tiene cuidado de no dejar esas cosas. Si es necesario, se pueden usar los objetos de guarda que saben cómo romper los ciclos en los objetos propios para asegurar automáticamente la destrucción adecuada. En algunos aspectos de la programación, esto puede ser importante, pero para la mayoría del trabajo diario, es irrelevante.

    
respondido por el Michael Kohne 27.12.2010 - 18:09
1

La limitación número uno de los punteros inteligentes es que no siempre ayudan contra referencias circulares. Por ejemplo, tiene el objeto A que almacena un puntero inteligente en el objeto B y el objeto B está almacenando un puntero inteligente en el objeto A. Si se los deja juntos sin restablecer ninguno de los punteros, nunca se los desasignará.

Esto sucede porque un puntero inteligente tiene que realizar una acción específica que no se activará en el escenario anterior porque ambos objetos no están disponibles para el programa. La recolección de basura se hará cargo, identificará correctamente que los objetos no son accesibles al programa y serán recopilados.

    
respondido por el sharptooth 27.12.2010 - 13:40
0

Recuerde que al final, todo se reduce a una CPU que ejecuta instrucciones. Por lo que sé, todas las CPU de nivel de consumidor tienen conjuntos de instrucciones que requieren que tenga datos almacenados en un lugar determinado en la memoria y tiene punteros a dichos datos. Eso es todo lo que tienes en el nivel básico.

Todo lo que está encima de eso con la recolección de basura, las referencias a los datos que pueden haberse movido, la compactación del montón, etc., está haciendo el trabajo dentro de las restricciones dadas por el paradigma anterior "fragmento de memoria con un puntero de dirección". Lo mismo ocurre con los punteros inteligentes: TODAVÍA tienes que hacer que el código se ejecute en el hardware real.

    
respondido por el user1249 27.12.2010 - 14:13

Lea otras preguntas en las etiquetas