5

La mayoría de los programadores coinciden en que la recolección de basura es una gran cosa, y en la mayoría de las aplicaciones vale la pena la sobrecarga. Sin embargo, mi observación personal es que la administración de la memoria para la mayoría de los objetos es trivial, y quizás 10% -20% de ellos representan la necesidad de kludges, como el recuento de referencias y los esquemas de administración de memoria realmente complicados en general. Me parece que uno puede obtener todos los beneficios de la recolección de basura con solo una pequeña fracción de la sobrecarga borrando conservativamente objetos grandes donde la vida del objeto es obvia y dejando que el GC recolecte el resto, suponiendo que la implementación de GC apoye tal cosa . Esto permitiría que el GC se ejecute con mucha menos frecuencia y consuma menos memoria excedente, al tiempo que se evitan casos que en realidad son difíciles de administrar de forma manual. Aún más interesante sería si el compilador inserta instrucciones delete deterministas automáticamente dónde vidas eran obvias:Una regla 90/10 para la gestión de memoria?

int myFunc() { 
    Foo[] foo = new Foo[arbitraryNumber]; // May be too big to stack allocate. 
    // do stuff such that the compiler can prove foo doesn't escape. 
    // foo is obviously no longer needed, can be automatically deleted here. 
    return someInteger; 
} 

Por supuesto, esto podría no funcionar bien con un GC copia, pero por el bien de este post vamos a asumir nuestra ISN GC copiando ¿Por qué estos esquemas híbridos de administración de memoria son aparentemente tan raros en los lenguajes de programación convencionales?

+1

Las aserciones vagas sobre 'observación personal' no ayudan a otros programadores. ¿Qué has * medido *? –

Respuesta

3

Porque este caso es demasiado raro. Casi ningún método está aislado. Todos aceptan objetos del exterior o crean objetos y los pasan.

Un método que no accede a ningún campo, no tiene parámetros y no devuelve algo, no puede hacer nada.

En su lugar, los GC se concentran en el caso más común (el 90%) y tratan de mantener esos 90% (los objetos temporales de vida corta) bajo control. Esto significa que en el caso común, tiene menos objetos para verificar y el resto no importa demasiado. Luego, usa un barrido incremental (para que pueda correr en pequeños sprints que solo interrumpen por un breve momento).

Una vez traté de encontrar un mejor algoritmo de GC y fallé miserablemente. Usan un enfoque que bordea lo arcano. El documento sobre Java 5 GC Performance Tuning debería darle algunas ideas. Y por supuesto está el GC article in Wikipedia.

Lo que se reduce a: El uso de un GC puede incluso ser más rápido que tener un esquema tradicional de asignación de memoria y liberación. Piense en el algoritmo clásico que simplemente ubica cualquier objeto alcanzable y lo copia a un lugar nuevo. Si acabas de olvidarte de un montón de objetos (digamos, el 90% de todos los objetos asignados), este algoritmo solo necesita verificar el resto (10%). Cualquier cosa que no pueda alcanzar, no importa cuánto pueda ser, no importará. Ahora puede decir que copiar es caro, pero a) esto no es cierto; hoy una CPU de escritorio promedio puede copiar 40MB en menos de 100ms yb) la copia le protegerá contra la fragmentación por lo que es realmente una buena cosa .

+0

Bien, error tonto. Básicamente estaba implicando una función pura, pero las funciones puras deberían devolver algo. Fijo. – dsimcha

+0

Revisa todo el código fuente que has escrito en tu vida. Dudo que encuentres muchos métodos que hacen algo útil * y * no funcionan en objetos de un ámbito externo. Además, como expliqué, los objetos vivos son caros, los objetos muertos son gratuitos. –

+1

En mi experiencia, los desarrolladores de aplicaciones ni siquiera son conscientes de la mitad de las asignaciones que hacen.Tal vez usted no estaba al tanto de estos triviales casos de basura porque no se dio cuenta de la cantidad de basura que hizo. – Aaron

1

Una nota rápida sobre la "obviamente ya no es necesario": No es tan fácil;)

[...] 
Foo() { 
    someGlobalList.add(this); 
} 
[...] 

Aparte de eso, su idea de ser capaz de eliminar manualmente grandes cositas es imo una buena. Tal como lo entiendo, se implementa al menos parcialmente en los idiomas modernos (por ejemplo, using en C#, lo que lamentablemente no es gratuito). Sin embargo, no en la medida que desee.

+0

sí, pero considere el caso de "cadena s1, s2, s3; ... s1 + s2 + s3". La memoria asignada durante la concatenación de s1 y s2 será basura antes del final de esta expresión, garantizada. - Entiendo que varios Lisp han tenido este tipo de optimizaciones. – Aaron

1

Lo que usted describe como sentencias de eliminación deterministas para objetos que no escapan Las implementaciones de GC modernas funcionan con bastante eficacia. La mayoría de los objetos asignados se asignan desde pools y se eliminan de manera muy eficiente al salir del método; muy pocos terminan en el montón de GC más lento.

Esto en efecto produce el mismo efecto que usted describe con menos intervención del programador.Y debido a que un recolector de basura es automático, permite menos posibilidades de error humano (¿qué pasaría si eliminara algo para lo cual todavía se conservara una referencia)?

0

Si está creando un objeto que espera que se use solo dentro del contexto de una llamada a método por vez y que no tiene finalización, entonces recomendaría usar un tipo de valor en lugar de un tipo de referencia en idiomas que hacer esta distinción Por ejemplo, en C# puede declarar su entidad como struct en lugar de class. Luego, las instancias locales de método de vida corta se asignarán en la pila en lugar de en el montón, y se desasignarán de la implícita en el momento en que se devuelva el método.

Y esta técnica puede ir aún más allá que su idea original, porque la estructura se puede pasar a otros métodos sin preocuparse por romper el análisis de vida útil.

Para una matriz, como en la pregunta, puede usar el comando stackalloc para lograr este efecto.

+0

Estaba pensando que esto huele sospechosamente similar a la pila frente a los objetos asignados al montón :) –

2

"La mayoría de los programadores coinciden en que la recolección de basura es una gran cosa, y en la mayoría de las aplicaciones vale la pena la sobrecarga".

generalizaciones ...

+0

En segundo lugar, esta afirmación. –

+0

Estoy de acuerdo en que la recolección de basura es una gran cosa, y en la mayoría de las aplicaciones vale la pena la sobrecarga. ;-) – teedyay

+0

No me malinterpreten, tanto GC como no GC tienen su lugar IMO. –

0

Ésta parece ser exactamente cómo D gestiona la memoria. Se recolectó su basura al tiempo que le permite eliminar objetos específicamente cuando lo desee, o incluso evitar el GC en conjunto a favor de malloc/free. La palabra clave con ámbito parece hacer lo que desea en la pila, aunque no estoy seguro de si realmente asigna el objeto en la pila o el montón.

0

Aunque puede haber algunos beneficios al combinar la limpieza manual con la recolección de basura, hay un beneficio sustancial para que el recolector de basura continúe administrando los objetos entre el momento en que ya no se necesitan y el tiempo en que se puede demostrar que no tienen sobreviviendo referencias enraizadas. Entre otras cosas, en un sistema que no es de GC, a menudo es muy difícil probar al eliminar un objeto que no existe una posible forma de que todavía pueda existir alguna referencia. Si se elimina un objeto mientras todavía existe una referencia, un intento de usar esa referencia podría causar un comportamiento indefinido arbitrario; protegerse contra tal peligro sin un GC es en general difícil. Por el contrario, si uno invalida objetos cuando uno termina con ellos, pero deja la reutilización de la memoria ocupada anteriormente en el GC, se puede asegurar que un intento de usar el objeto invalidado fallará de manera predecible.

Dicho sea de paso, si estuviera diseñando un "microarmado", tendría áreas de almacenamiento separadas para objetos mutables e inmutables. La recolección de basura generacional funciona mejor si el código puede decir si un objeto se ha escrito desde que se recopiló gen0 o gen1. Hacer tal determinación con objetos mutables es mucho más difícil que con objetos inmutables. Por otro lado, administrar manualmente la vida útil de los objetos mutables es generalmente más fácil que administrar la vida útil de los objetos inmutables, ya que los primeros generalmente deben tener un "propietario" claro, mientras que los segundos generalmente no.

Cuestiones relacionadas