En estos días, se recolectan muchos idiomas. Incluso está disponible para C ++ por terceros. Pero C ++ tiene RAII y punteros inteligentes. Entonces, ¿qué sentido tiene 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 a un lado el RAII), por especificación y por implementación, ¿habrá alguna necesidad de recolectores de basura? Si no, ¿por qué no es así?
garbage-collection
smart-pointer
Gulshan
fuente
fuente
Respuestas:
Supongo que te refieres a los punteros inteligentes contados de referencia y notaré 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 los punteros inteligentes contados de referencia"? en lugar.
Precisión . El recuento de referencias solo pierde ciclos, por lo que los punteros inteligentes contados con referencias perderán memoria en general a menos que se agreguen otras técnicas para capturar los ciclos. 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 rastreo de referencia basados en el alcance recopilan valores en diferentes momentos, a veces el conteo de referencias se recolecta antes y a veces los GC de rastreo se recopilan 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 generan atómicamente. Existen técnicas avanzadas de conteo de referencias diseñadas para aliviar esto, pero los GC de rastreo siguen siendo el algoritmo de elección en entornos de producción.
Latencia . Las implementaciones típicas de puntero inteligente permiten que los destructores se avalanchen, 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, la cinta de correr de Baker.
fuente
Como nadie lo ha mirado desde este ángulo, reformularé su pregunta: ¿por qué poner algo en el idioma si puede hacerlo en una biblioteca? Ignorando la implementación específica y los detalles sintácticos, GC / punteros inteligentes es básicamente un caso especial de esa pregunta. ¿Por qué definir un recolector de basura en el propio lenguaje si puede implementarlo en una biblioteca?
Hay un par de respuestas a esa pregunta. Lo más importante primero:
Se asegura de que todo el código pueda usarlo para interoperar. Esta es, creo, la gran razón por la cual la reutilización de código y el intercambio de código realmente no despegaron hasta Java / C # / Python / Ruby. Las bibliotecas necesitan comunicarse, y el único idioma compartido confiable que tienen es el contenido de la especificación del idioma en sí (y, hasta cierto punto, su biblioteca estándar). Si alguna vez ha intentado reutilizar bibliotecas en C ++, es probable que haya experimentado el horrible dolor que no causa la semántica de memoria estándar. Quiero pasar una estructura a alguna lib. ¿Paso una referencia? ¿Puntero?
scoped_ptr
?smart_ptr
? ¿Estoy pasando la propiedad, o no? ¿Hay alguna manera de indicar eso? ¿Qué pasa si la biblioteca necesita asignar? ¿Tengo que darle un asignador? Al no hacer que la administración de memoria forme parte del lenguaje, C ++ obliga a cada par de bibliotecas a negociar aquí su propia estrategia específica, y es realmente difícil lograr que todos estén de acuerdo. GC lo convierte en un completo problema.Puede diseñar la sintaxis a su alrededor. Debido a que C ++ no encapsula la administración de memoria en sí misma, tiene que proporcionar una gama de enlaces sintácticos para permitir que el código a nivel de usuario exprese todos los detalles. Tiene punteros, referencias,
const
operadores de desreferenciación, operadores de indirección, dirección de dirección, etc. Si aplica la administración de memoria al lenguaje en sí, la sintaxis puede diseñarse en torno a eso. Todos esos operadores desaparecen y el lenguaje se vuelve más limpio y simple.Obtiene un alto retorno de la inversión. El valor que genera cualquier fragmento de código se multiplica por el número de personas que lo utilizan. Esto significa que cuantos más usuarios tenga, más puede permitirse gastar en una pieza de software. Cuando mueva una función al idioma, todos los usuarios del idioma la usarán. Esto significa que puede asignarle más esfuerzo del que podría a una biblioteca solo utilizada por un subconjunto de esos usuarios. Es por eso que lenguajes como Java y C # tienen máquinas virtuales absolutamente de primer nivel y recolectores de basura fantásticamente de alta calidad: el costo de desarrollarlos se amortiza en millones de usuarios.
fuente
Dispose
un objeto que encapsula un mapa de bits, cualquier referencia a ese objeto será una referencia a un objeto de mapa de bits dispuesto. Si el objeto se eliminó prematuramente mientras otro código todavía espera usarlo, la clase de mapa de bits puede garantizar que el otro código falle de manera predecible . Por el contrario, usar una referencia a la memoria liberada es Comportamiento indefinido.La recolección de basura básicamente significa que los objetos asignados se liberan automáticamente en algún momento después de que ya no se puede acceder a ellos.
Más exactamente, se lanzan cuando se vuelven inalcanzables para el programa, ya que los objetos referenciados circularmente nunca se liberarían de otra manera.
Los punteros inteligentes solo se refieren a cualquier estructura que se comporta como un puntero ordinario pero tiene alguna funcionalidad adicional adjunta. Estos incluyen , entre otros, desasignación, sino también copia en escritura, cheques encuadernados, ...
Ahora, como ha dicho, los punteros inteligentes se pueden usar para implementar una forma de recolección de basura.
Pero el tren del pensamiento va de la siguiente manera:
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 lanzará cuando las referencias caigan 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 de 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 abandona el ámbito en el que se ha creado, se llama a su destructor. En combinación con las plantillas y la sobrecarga del operador, puede diseñar un objeto contenedor que se comporte como un puntero, pero utilice la funcionalidad de destructor para limpiar los recursos adjuntos (RAII). Llamas a este un puntero inteligente .
Todo esto es altamente específico de C ++: sobrecarga del operador, plantillas, destructores, ... En esta situación de lenguaje en particular, ha desarrollado punteros inteligentes para proporcionarle el GC que desea.
Pero si diseña un lenguaje con GC desde el principio, esto es simplemente un detalle de implementación. Simplemente dice que el objeto se limpiará y el compilador lo hará por usted.
Los punteros inteligentes como en C ++ probablemente ni siquiera serían posibles en lenguajes como C #, que no tienen ninguna destrucción determinista en absoluto (C # lo soluciona al proporcionar azúcar sintáctico para llamar
.Dispose()
a ciertos objetos). Los recursos no referenciados finalmente serán reclamados por el GC, pero no se definió cuándo ocurrirá 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 están configurados encima, el GC .NET puede, por ejemplo, retrasar las operaciones de memoria y realizarlas en bloques para que sean más baratas o incluso mover la memoria para aumentar la eficiencia en función de la frecuencia con la que los objetos Se accede.
fuente
IDisposable
yusing
. Pero requiere un poco de esfuerzo del programador, por lo que generalmente solo se usa para recursos muy escasos, como los identificadores de conexión de la base de datos.IDisposable
sintaxis más simple simplemente reemplazando la convencionallet ident = value
poruse ident = value
...using
no tiene nada que ver con la recolección de basura, simplemente llama a una función cuando una variable cae fuera del alcance al igual que los destructores en C ++.En mi opinión, hay dos grandes diferencias entre la recolección de basura y los punteros inteligentes que se usan para la administración de memoria:
Lo primero significa que GC recolectará basura que los punteros inteligentes no lo harán; Si está utilizando punteros inteligentes, debe evitar crear este tipo de basura o estar preparado para manejarlo manualmente.
Esto último significa que no importa cuán inteligentes sean los punteros inteligentes, su funcionamiento ralentizará los hilos de trabajo en su programa. La recolección de basura puede diferir el trabajo y moverlo a otros hilos; eso le permite ser más eficiente en general (de hecho, el costo de tiempo de ejecución de un GC moderno es menor que un sistema libre / malloc normal, incluso sin la sobrecarga adicional de los punteros inteligentes), y hacer el trabajo que aún necesita hacer sin entrar en el forma de los hilos de la aplicación.
Ahora, tenga en cuenta que los punteros inteligentes, al ser construcciones programáticas, se pueden usar para hacer todo tipo de otras cosas interesantes, vea la respuesta de Dario, que están completamente fuera del alcance de la recolección de basura. Si desea hacer eso, necesitará punteros inteligentes.
Sin embargo, para fines de 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.
fuente
using
bloqueo en versiones posteriores de C #. Además, el comportamiento no determinista de los GC puede ser prohibitivo en los sistemas en tiempo real (razón por la cual los GC no se usan allí). Además, no olvidemos que los GC son tan complejos para acertar que la mayoría pierden memoria y son bastante ineficientes (por ejemplo, Boehm ...).El término recolección de basura implica que hay basura para recolectar. En C ++, los punteros inteligentes vienen en múltiples sabores, lo más importante es el unique_ptr. El unique_ptr es básicamente una construcción de propiedad y alcance único. En un código bien diseñado, la mayoría de las cosas asignadas al montón normalmente residen 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 conducían a las personas a los 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 al código a usar una propiedad única y bien definida en cualquier momento se vuelven más importantes para el rendimiento.
Incluso en un programa bien diseñado, especialmente en entornos de subprocesos múltiples, no todo se puede expresar 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 problemas de por vida en una configuración de un solo subproceso, en una configuración de subprocesos múltiples 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 esto en C ++ es el único lugar donde vemos basura, pero en cantidades tan pequeñas que un programa de C ++ diseñado adecuadamente debería considerarse más para implementar la recolección de 'basura' con ptr's compartidos que la recolección de basura completa como implementado en otros idiomas. C ++ simplemente no tiene tanta "basura"
Como han dicho otros, los punteros inteligentes contados por referencia 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 contadas por referencia es el problema con la creación de estructuras de datos huérfanos conectadas entre sí con punteros inteligentes que crean grupos de objetos que evitan que se recopilen entre sí. Mientras que en un programa diseñado de acuerdo con el modelo de cómputo del actor, las estructuras de datos generalmente no permiten que surjan tales grupos no recopilables en C ++, cuando se usa el enfoque de datos compartidos amplios para la programación de subprocesos múltiples, como se usa predominantemente en gran parte de la industria, estos grupos huérfanos pueden convertirse rápidamente en realidad.
Entonces, para resumirlo todo, si por el uso de puntero compartido te refieres al uso amplio de unique_ptr combinado con el modelo de actor de enfoque de computación para programación multihilo y el uso limitado de shared_ptr, que otras formas de recolección de basura no te compran Beneficios añadidos. Sin embargo, si un enfoque de todo lo compartido le permitiera terminar con shared_ptr por todas partes, entonces debería considerar cambiar los modelos de simultaneidad o cambiar a un lenguaje administrado que esté más orientado hacia un mayor intercambio de propiedad y acceso concurrente a las estructuras de datos.
fuente
Rust
que no necesita recolección de basura?La mayoría de los punteros inteligentes se implementan utilizando el recuento de referencias. Es decir, cada puntero inteligente que se refiere a un objeto incrementa el recuento de referencias de los objetos. Cuando ese recuento llega a cero, se libera el objeto.
El problema es 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, entonces para liberar la memoria asociada con A, B y C necesita manualmente obtener una "ruptura" de la referencia circular (p. ej.,
weak_ptr
en C ++).La recolección de basura (típicamente) funciona de manera bastante diferente. La mayoría de los recolectores de basura en estos días utilizan una prueba de accesibilidad . Es decir, analiza todas las referencias en la pila y las que son accesibles globalmente y luego rastrea cada objeto al que hacen referencia esas referencias y los objetos a los que se refieren, etc. Todo lo demás es basura.
De ese modo, las referencias circulares ya no importan más - siempre y cuando ni A, B y C son accesibles , la memoria puede ser reclamado.
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 memoria de la forma que desee, 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 en el montón suele ser menor que un lenguaje de referencia como C # /. NET. Pero eso no es realmente un problema de recolección de basura versus punteros inteligentes.
En cualquier caso, el problema no es cortar y secar, uno es mejor que el otro. Cada uno tiene ventajas y desventajas.
fuente
Se trata de rendimiento . La asignación de memoria requiere mucha administración. Si la asignación se ejecuta en segundo plano, aumenta el rendimiento del proceso en primer plano. Desafortunadamente, la asignación de memoria no puede ser perezosa (los objetos asignados se usarán en el momento sagrado siguiente), pero la liberación de objetos sí.
Pruebe en C ++ (sin GC) para asignar un gran grupo de objetos, imprima "hola" y luego bórrelos. Te sorprenderá cuánto tiempo lleva liberar objetos.
Además, GNU libc proporciona herramientas más efectivas para la asignación de memoria, vea los obstáculos . Debo notar que no tengo experiencia con obstáculos, nunca los usé.
fuente
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 a la vez. En general, esto dará como resultado que se gaste menos CPU en general en la desasignación de memoria, pero significa que tendrá un gran estallido de actividad de desasignación en algún momento. 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 referencias) tienen la ventaja de que suceden exactamente cuando se espera al mirar el código (el puntero inteligente se sale del alcance, la cosa se elimina). Obtienes pequeñas explosiones de desasignación aquí y allá. En general, puede usar más tiempo de CPU en la desasignación, pero dado que se extiende sobre todas las cosas que suceden en su programa, es menos probable (excluyendo la desasignación de alguna estructura de datos de monstruos) que sea visible para su usuario.
Si está haciendo algo donde la capacidad de respuesta es importante, le sugiero que los punteros inteligentes / el conteo de referencias le permitan saber exactamente cuándo están sucediendo las cosas, para que pueda saber mientras codifica lo que probablemente será visible para sus usuarios. En una configuración de GC solo tiene el control más efímero sobre el recolector de basura y simplemente tiene que intentar solucionar el problema.
Por otro lado, si su rendimiento general es su objetivo, 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 a las estructuras de datos que no tienen ciclos, o simplemente tiene cuidado con cómo dejar ir esas cosas. Si es necesario, se pueden usar objetos de mantenimiento que sepan cómo romper los ciclos en los objetos de propiedad para asegurar automáticamente la destrucción adecuada. En algunos ámbitos de la programación, esto puede ser importante, pero para la mayoría del trabajo diario es irrelevante.
fuente
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 al objeto B y el objeto B está almacenando un puntero inteligente al objeto A. Si se dejan juntos sin restablecer ninguno de los punteros, nunca se desasignarán.
Esto sucede porque un puntero inteligente tiene que realizar una acción específica que no será triigered en el escenario anterior porque ambos objetos son inalcanzables para el programa. La recolección de basura se las arreglará: identificará adecuadamente que los objetos no son alcanzables para el programa y serán recolectados.
fuente
Es un espectro .
Si no tiene límites estrictos en cuanto al rendimiento y está preparado para trabajar duro, terminará en la asamblea o c, con toda la responsabilidad de tomar las decisiones correctas y toda la libertad para hacerlo, pero con eso , toda la libertad para estropearlo:
"Te diré qué hacer, hazlo. Confía en mí".
La recolección de basura es el otro extremo del espectro. Usted tiene muy poco control, pero se encarga de usted:
"Te diré lo que quiero, lo haces realidad".
Esto tiene muchas ventajas, sobre todo porque no es necesario ser tan confiable cuando se trata de saber exactamente cuándo un recurso ya no es necesario, pero (a pesar de algunas de las respuestas que circulan por aquí) no es bueno para el rendimiento, y La previsibilidad del rendimiento. (Como todas las cosas, si se le da el control y hace algo estúpido, puede tener peores resultados. Sin embargo, sugerir que saber en tiempo de compilación cuáles son las condiciones para poder liberar memoria, no se puede usar como una ganancia de rendimiento es más que ingenuo).
RAII, alcance, recuento de referencias, etc. son todos ayudantes para permitirle avanzar más a lo largo de ese espectro, pero no todo el camino. Todas estas cosas aún requieren un uso activo. Todavía permiten y requieren que interactúe con la administración de memoria de una manera que la recolección de basura no lo hace.
fuente
Recuerde que al final, todo se reduce a una CPU que ejecuta las instrucciones. Que yo sepa, todas las CPU de grado de consumidor tienen conjuntos de instrucciones que requieren que tenga datos almacenados en un lugar determinado en la memoria y que tenga punteros a dichos datos. Eso es todo lo que tienes en el nivel básico.
Todo esto además de la recolección de basura, las referencias a los datos que pueden haberse movido, la compactación del montón, etc., 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 con los punteros inteligentes: TODAVÍA tiene que hacer que el código se ejecute en hardware real.
fuente