memory-management - tiradero - servicio de recolección de basura
¿Cuándo no quieres usar la recolección de basura? (14)
La recolección de basura ha existido desde los primeros días de LISP, y ahora, varias décadas después, la mayoría de los lenguajes de programación modernos lo utilizan.
Suponiendo que está utilizando uno de estos idiomas, ¿qué razones tendría para no utilizar la recolección de elementos no utilizados, y en su lugar administrar manualmente las asignaciones de memoria de alguna manera?
¿Alguna vez has tenido que hacer esto?
Por favor dé ejemplos sólidos si es posible.
Asignaciones de memoria? No, creo que el GC es mejor que yo.
¿Pero las asignaciones de recursos escasos, como manejadores de archivos, conexiones de bases de datos, etc.? Escribo el código para cerrarlos cuando termine. GC no hará eso por ti.
En teoría, nada. Sin embargo, en la práctica, no lo use si no funciona para su aplicación.
Diferentes algoritmos GC pueden o no ser eficientes para diferentes tipos de aplicaciones. Algunos GC son mejores para las aplicaciones de larga ejecución, algunos están ajustados para el rendimiento, algunos están ajustados para reducir la latencia, y algunos simplemente apestan en general.
Tuve algunas instancias en las que el GC de Java era menos eficiente, y deseé poder administrar mi propia memoria. Básicamente, estaba usando una TONELADA de memoria que se convirtió en basura de inmediato, y debido a la forma en que funcionaba el GC, algo de eso terminaba en una generación "tenured" cuando no era necesario y no puedo forzar a Java. para usar la colección de copias para toda su memoria.
Tener 16 gigas de ram en lugar de 8 probablemente también habría solucionado el problema. Con todo, solo tenía que hacer un ajuste extra para que funcionara, y como no puedo apagar el gc en Java, era mi única opción.
Sospecho que el nuevo GC de Java 7 habría solucionado mi problema.
Las aplicaciones en tiempo real son probablemente difíciles de escribir con un recolector de basura. Tal vez con un GC incremental que funcione en otro hilo, pero esto es una sobrecarga adicional.
Puedo pensar en algunos:
Desasignación / limpieza determinista
Sistemas en tiempo real
Sin renunciar a la mitad de la memoria o al tiempo del procesador, según el algoritmo
Asignación / asignación de memoria más rápida / dealloc y asignación específica de aplicaciones, desasignación y administración de memoria. Básicamente, escribir sus propias cosas de memoria, generalmente para aplicaciones sensibles al rendimiento. Esto se puede hacer donde el comportamiento de la aplicación se entiende bastante bien. Para uso general GC (como para Java y C #) esto no es posible.
EDITAR
Dicho esto, GC ciertamente ha sido bueno para gran parte de la comunidad. Nos permite enfocarnos más en el dominio del problema en lugar de ingeniosos trucos o patrones de programación. Todavía soy un desarrollador de C ++ "no administrado". Las buenas prácticas y herramientas ayudan en ese caso.
Se puede pensar en un caso cuando se trata de grandes conjuntos de datos que equivalen a cientos de megabytes o más. Dependiendo de la situación, es posible que desee liberar esta memoria tan pronto como haya terminado con ella, para que otras aplicaciones puedan usarla.
Además, cuando se trata de algún código no administrado, puede haber una situación en la que es posible que desee evitar que el GC recopile algunos datos porque todavía está siendo utilizado por la parte no administrada. Aunque todavía tengo que pensar en una buena razón por la que simplemente mantener una referencia a ella podría no ser lo suficientemente bueno. :PAG
Una situación con la que me he enfrentado es el procesamiento de imágenes. Mientras trabajaba en un algoritmo para recortar imágenes, descubrí que las bibliotecas administradas simplemente no son lo suficientemente rápidas como para cortarlas en imágenes grandes o en múltiples imágenes a la vez.
La única forma de procesar en una imagen a una velocidad razonable era usar código no administrado en mi situación. Esto fue mientras trabajaba en un pequeño proyecto personal en C # .NET donde no quería aprender una biblioteca de terceros debido al tamaño del proyecto y porque quería aprender a mejorarlo. Puede haber una biblioteca de terceros (tal vez Paint.NET) que podría hacerlo, pero aún requeriría un código no administrado.
Casi todas estas respuestas se reducen a rendimiento y control. Un ángulo que no he visto en las publicaciones anteriores es que saltarse GC le da a su aplicación un comportamiento de memoria caché más predecible de dos maneras.
- En ciertas aplicaciones sensibles a la caché, tener el idioma automáticamente basura su caché de vez en cuando (aunque esto depende de la implementación) puede ser un problema.
- Aunque GC es ortogonal a la asignación, la mayoría de las implementaciones le dan menos control sobre los detalles. Una gran cantidad de código de alto rendimiento tiene estructuras de datos sintonizadas para cachés, y la implementación de cosas como los algoritmos de caché ajena requiere un control más preciso sobre el diseño de la memoria. Aunque conceptualmente no hay ninguna razón por la que GC sea incompatible con la especificación manual del diseño de la memoria, no puedo pensar en una implementación popular que te permita hacerlo.
Dos palabras: Space Hardening
Sé que es un caso extremo, pero aún aplicable. Uno de los estándares de codificación que se aplica al núcleo de los rovers de Marte en realidad prohíbe la asignación de memoria dinámica. Si bien esto es realmente extremo, ilustra un ideal de "implementar y olvidarse de él sin preocupaciones".
En resumen, tenga algo de sentido sobre lo que su código realmente está haciendo a la computadora de alguien. Si lo haces, y eres conservador ... deja que el hada de la memoria se encargue del resto. Mientras desarrolla en un núcleo cuádruple, su usuario puede estar en algo mucho más antiguo, con mucha menos memoria de sobra.
Use la recolección de basura como una red de seguridad, tenga en cuenta lo que asigna.
Hago un montón de desarrollo integrado, donde la pregunta es si usar malloc o asignación estática y la recolección de basura no es una opción.
También escribo muchas herramientas de soporte basadas en PC y usaré GC cuando esté disponible y sea lo suficientemente rápido, lo que significa que no tengo que usar pedant :: std :: string.
Escribo un montón de código de compresión y cifrado y el rendimiento del GC generalmente no es lo suficientemente bueno a menos que realmente doble la implementación. GC también requiere que tenga mucho cuidado con los trucos de aliasing de direcciones. Normalmente escribo código sensible al rendimiento en C y lo llamo desde la interfaz de Python / C #.
Así que mi respuesta es que hay razones para evitar GC, pero la razón es casi siempre el rendimiento y, por lo tanto, es mejor codificar las cosas que lo necesitan en otro idioma en lugar de intentar engañar al GC.
Si desarrollo algo en MSVC ++, nunca uso la recolección de basura. En parte porque no es estándar, pero también porque crecí sin GC en C ++ y diseño automáticamente en recuperación de memoria segura. Habiendo dicho esto, creo que C ++ es una abominación que no ofrece la transparencia de traducción y la predictibilidad de C o la seguridad de la memoria de ámbito (entre otras cosas) de los lenguajes de OO posteriores.
Hay dos tipos principales de sistemas en tiempo real, duro y blando. La principal diferencia es que los sistemas duros en tiempo real requieren que un algoritmo siempre termine en un presupuesto de tiempo particular, donde como un sistema suave le gustaría que suceda normalmente. Los sistemas blandos pueden utilizar colectores de basura bien diseñados, aunque uno normal no sería aceptable. Sin embargo, si un algoritmo del sistema en tiempo real no se completa a tiempo, entonces las vidas podrían estar en peligro. Encontrará este tipo de sistemas en reactores nucleares, aviones y transbordadores espaciales e incluso solo en el software especializado del que están hechos los sistemas operativos y los controladores. Basta con decir que este no es su trabajo de programación común.
Las personas que escriben estos sistemas no tienden a usar lenguajes de programación de propósito general. Ada fue diseñado con el propósito de escribir este tipo de sistemas en tiempo real. A pesar de ser un lenguaje especial para tales sistemas en algunos sistemas, el lenguaje se reduce a un subconjunto conocido como Spark. Spark es un subconjunto crítico de seguridad especial del lenguaje Ada y una de las características que no permite es la creación de un nuevo objeto. La nueva palabra clave para objetos está totalmente prohibida por su potencial para quedarse sin memoria y su tiempo de ejecución variable. De hecho, todo el acceso a memoria en Spark se realiza con ubicaciones de memoria absoluta o variables de pila y no se realizan nuevas asignaciones en el montón. Un recolector de basura no solo es totalmente inútil sino dañino para el tiempo de ejecución garantizado.
Este tipo de sistemas no son exactamente comunes, pero cuando existen, se requieren algunas técnicas de programación muy especiales y los tiempos de ejecución garantizados son críticos.
No entiendo muy bien la pregunta. Como preguntas sobre un idioma que usa GC, supongo que estás pidiendo ejemplos como
- Retener deliberadamente una referencia incluso cuando sé que está muerta, tal vez para reutilizar el objeto para satisfacer una futura solicitud de asignación.
- Mantenga un registro de algunos objetos y ciérrelos explícitamente, ya que contienen recursos que no se pueden administrar fácilmente con el recolector de elementos no utilizados (descriptores de archivos abiertos, ventanas en la pantalla, ese tipo de cosas).
Nunca he encontrado una razón para hacer el n. ° 1, pero el n. ° 2 es uno que aparece ocasionalmente. Muchos recolectores de basura ofrecen mecanismos para la finalización , que es una acción que se vincula a un objeto y el sistema ejecuta esa acción antes de reclamar el objeto. Pero muchas veces el sistema no proporciona garantías sobre si los finalizadores realmente se ejecutan, por lo que la finalización puede ser de utilidad limitada.
Lo principal que hago en un lenguaje recogido de basura es vigilar de cerca el número de asignaciones por unidad de trabajo que hago. La asignación suele ser el cuello de botella de rendimiento, especialmente en sistemas Java o .NET. Es un problema menor en idiomas como ML, Haskell o LISP, que generalmente están diseñados con la idea de que el programa se va a asignar como loco.
EDITAR : respuesta más larga para comentar.
No todos entienden que cuando se trata de rendimiento, el asignador y el GC deben considerarse como un equipo. En un sistema de vanguardia, la asignación se realiza desde el espacio libre contiguo (el "vivero") y es tan rápido como la prueba y el incremento. Pero a menos que el objeto asignado sea increíblemente efímero, el objeto incurre en una deuda en el futuro: debe ser copiado del vivero, y si vive un tiempo, puede ser copiado a través de varias generaciones. Los mejores sistemas utilizan espacio libre contiguo para la asignación y en algún punto cambian de copiar a marcar / barrer o marcar / escanear / compactar para objetos más antiguos. Entonces, si eres muy exigente, puedes salirte con la tuya ignorando las asignaciones si
- Usted sabe que está tratando con un sistema de vanguardia que asigna desde el espacio libre continuo (un vivero).
- Los objetos que asigna son muy efímeros (menos de un ciclo de asignación en el vivero).
De lo contrario, los objetos asignados pueden ser inicialmente baratos, pero representan un trabajo que debe realizarse más adelante. Incluso si el costo de la asignación en sí es una prueba e incremento, la reducción de las asignaciones sigue siendo la mejor manera de mejorar el rendimiento. He sintonizado docenas de programas de LD usando asignadores y coleccionistas de última generación y esto sigue siendo cierto; incluso con la mejor tecnología, la gestión de memoria es un cuello de botella de rendimiento común .
Y te sorprendería saber cuántos asignadores no funcionan bien incluso con objetos de muy corta vida. Acabo de obtener una gran aceleración de Lua 5.1.4 (probablemente el más rápido del lenguaje de scripting, con un GC generacional) al reemplazar una secuencia de 30 sustituciones, cada una de las cuales asignó una copia nueva de una expresión grande, con una sustitución simultánea de 30 nombres, que asignaron una copia de la expresión grande en lugar de 30. El problema de rendimiento desapareció.
En los videojuegos, no desea ejecutar el recolector de basura entre un marco de juego.
Por ejemplo, Big Bad está frente a ti y tienes 10 vidas. Decidiste correr hacia el poder de Quad Damage. Tan pronto como retires el poder, te preparas para girar hacia tu enemigo y disparar con tu arma más poderosa.
Cuando el powerup desapareció, sería una mala idea ejecutar el recolector de basura solo porque el mundo del juego tiene que eliminar los datos para el encendido.
Por lo general, los videojuegos manejan sus objetos averiguando qué se necesita en un determinado mapa (por eso lleva un tiempo cargar mapas con muchos objetos). Algunos motores de juegos llamarían al recolector de basura después de ciertos eventos (después de guardarlos, cuando el motor detecte que no hay amenaza en las inmediaciones, etc.).
Además de los videojuegos, no encuentro ninguna buena razón para desactivar la recolección de basura.
Editar: Después de leer los otros comentarios, me di cuenta de que los sistemas integrados y Space Hardening (los comentarios de Bill y tinkertim, respectivamente) también son buenas razones para desactivar el recolector de basura.
Suponiendo que está utilizando uno de estos idiomas, ¿qué razones tendría para no utilizar la recolección de elementos no utilizados, y en su lugar administrar manualmente las asignaciones de memoria de alguna manera?
Potencialmente, varias razones posibles:
La latencia del programa debido al recolector de basura es inaceptablemente alta.
El retraso antes del reciclado es inaceptablemente largo, por ejemplo, la asignación de un gran conjunto en .NET lo coloca en el Large Object Heap (LOH) que se recoge con poca frecuencia por lo que se mantendrá durante un tiempo después de que se haya vuelto inalcanzable.
Otros gastos generales relacionados con la recolección de basura son inaceptablemente altos, por ejemplo, la barrera de escritura.
Las características del recolector de basura son inaceptables, por ejemplo, al redistribuir las matrices en fragmentos .NET, el Large Object Heap (LOH) provoca la falta de memoria cuando se agota el espacio de direcciones de 32 bits, aunque teóricamente hay mucho espacio libre. En OCaml (y probablemente en la mayoría de los lenguajes de GC), las funciones con pilas de subprocesos profundas se ejecutan de forma asintótica más lenta. También en OCaml, se impide que los subprocesos se ejecuten en paralelo mediante un bloqueo global en el GC, de modo que (en teoría) el paralelismo se puede lograr bajando a C y utilizando la gestión de memoria manual.
¿Alguna vez has tenido que hacer esto?
No, nunca tuve que hacer eso. Lo he hecho por diversión Por ejemplo, escribí un recolector de basura en F # (un lenguaje .NET) y, para hacer que mis tiempos sean representativos, adopté un estilo sin atribuciones para evitar la latencia del GC. En el código de producción, tuve que optimizar mis programas usando el conocimiento de cómo funciona el recolector de basura, pero nunca tuve que eludirlo desde .NET, y mucho menos descartar .NET por completo porque impone un GC.
Lo más cerca que he estado de dejar caer la recolección de basura fue dejar caer el idioma OCaml en sí porque su GC impide el paralelismo. Sin embargo, terminé migrando a F #, que es un lenguaje .NET y, en consecuencia, hereda el excelente GC compatible con múltiples núcleos del CLR.
Mientras más crítica sea la ejecución, más se desea posponer la recolección de basura, pero cuanto más tiempo posponga la recolección de basura, mayor será el problema con el tiempo.
Usa el contexto para determinar la necesidad:
1.
- Se supone que la recolección de basura protege contra fugas de memoria
- ¿Necesita más estado de lo que puede administrar en su cabeza?
2.
- Devolver la memoria destruyendo objetos sin referencias puede ser impredecible
- ¿Necesitas más indicadores de los que puedes manejar en tu cabeza?
3.
- La inanición de recursos puede ser causada por la recolección de basura
- ¿Tiene más CPU y memoria de la que puede administrar en su cabeza?
4.
- La recolección de basura no puede abordar archivos y sockets
- ¿Tiene E / S como su principal preocupación?
En los sistemas que utilizan la recolección de elementos no utilizados, a veces se utilizan indicadores débiles para implementar un mecanismo simple de almacenamiento en caché porque los objetos sin referencias fuertes se desasignan solo cuando la presión de la memoria desencadena la recolección de elementos no utilizados. Sin embargo, con ARC, los valores se desasignan tan pronto como se elimina su última referencia fuerte, por lo que las referencias débiles no son adecuadas para tal fin.
Referencias
- Preguntas frecuentes sobre GC
- Pautas Smart Pointer
- Transición a las notas de la versión de ARC
- Recolección de basura precisa con LLVM
- Gestión de memoria en varios idiomas
- jwz en la recolección de basura
- Apple podría alimentar la Web
- ¿Cómo funcionan los recolectores de basura de secuencias de comandos?
- Minimice la Generación de Basura: GC es su Amigo, no su Servidor
- Recolección de basura en IE6
- Rendimiento lento del navegador web cuando ve una página web que usa JScript en Internet Explorer 6
- Transición a notas de versión de ARC: ¿Qué clases no admiten referencias débiles?
- Recuento automático de referencias: referencias débiles