c++ - ¿Cuándo puede ser más rápida la recolección de basura que la administración manual de memoria?
garbage-collection raii (3)
Hay un escenario particular que conozco en el que el puntero del GC es mucho más rápido que un puntero inteligente (puntero contado de referencia) cuando ambos se implementan en C ++ tradicional (es decir, no es C ++ / CLR, ya que no tendremos el mismo lujo de compactar la memoria después). Mark-Sweep, y estamos tratando de comparar manzanas con manzanas aquí tanto como podamos, suponiendo que GC use el mismo administrador de memoria de pila subyacente. Es cuando su tiempo de asignación de objetos supera significativamente el tiempo de creación de objetos.
Los ejemplos incluyen ordenación, inversión de matrices, etc.
Consulte aquí para obtener más información sobre mi prueba con un marco de GC que implementé utilizando los punteros contados de referencia de C ++ vs tradicionales . El resultado de la prueba de reversión de la matriz fue que GcString fue 16.5 veces más rápido que la cadena de referencia ref.
Esto podría atribuirse a la semántica de bloqueo de bus, dolorosamente lenta, en un puntero de referencia (a menos que esté apuntando a aplicaciones puramente de un solo subproceso, se requieren bloqueos para la seguridad del hilo). Por mi experiencia en la implementación de un GC preciso de alto rendimiento en C ++ tradicional, puedo decir que hay más oportunidades de optimización con un GC vs ''manual'' (según la definición de OP del manual) de gestión de memoria (es decir, punteros inteligentes).
¿En qué circunstancias es más eficiente la recolección de basura que la administración manual de memoria? (Aquí, el manual podría significar usar malloc y gratis como en C, o el limpiador RAII y las técnicas de puntero inteligente popularizadas por C ++)
Me gusta cómo la recolección de basura puede eliminar cierta complejidad accidental del software de escritura, pero me complació aún más cómo RAII y los punteros inteligentes pueden eliminar esa complejidad al tiempo que trabajan en otros recursos distintos de la memoria, son deterministas, ofrecen garantías de rendimiento y son más eficientes. en general. Así que pensé que podía ignorar con seguridad la recolección de basura. Sin embargo, me he dado cuenta de que la gente ha estado diciendo que la recolección de basura es más rápida que la administración de recursos ajustada que se usa en C ++, como cuando hay mucha memoria adicional disponible.
Entonces, ¿cuándo exactamente puede la recolección de basura superar el manejo manual de la memoria? Me gustan los punteros RAII e inteligentes, pero me gustaría aceptar la recolección de basura como otra herramienta si es más rápida.
Nunca, y puedo probarlo.
Primero, asumamos que estamos usando los mejores algoritmos en cualquier caso. El uso de un algoritmo subóptimo puede probar cualquier cosa.
En segundo lugar, supongamos que el mejor algoritmo GC usa los tiempos T0...Tn
para decidir si la asignación de memoria i
debería liberarse en un momento determinado. El total entonces es Σ(Ti)
Ahora, existe un algoritmo de administración de memoria manual equivalente que utiliza el mismo algoritmo de GC, pero solo considera los bloques de memoria que se han marcado manualmente para ser liberados. Por lo tanto, el tiempo de ejecución es Σ(δiTi)
(con δi = 0 cuando el bloque i no fue liberado, y = 1 cuando lo fue).
Obviamente, Σ(δiTi) ≤ Σ(Ti)
: hay un algoritmo manual que no es estrictamente peor que un algoritmo GC.
¿Cómo contrasta eso con otras respuestas?
- "Con RAII, tienes que desasignar cada vez que asignas". - No, eso es un supuesto erróneo. Debemos comparar las operaciones por lotes o no por lotes. GC sería mucho peor si también hicieras una carrera de GC cada vez que algo saliera del alcance.
- "Localidad mejorada" : bueno, a menos que descartes el hecho de que los idiomas que no son de GC pueden usar la pila con mucha más frecuencia, y esa pila tiene una excelente ubicación de referencia.
- "bajas probabilidades de fugas" : eso es totalmente cierto, pero estábamos discutiendo el rendimiento en tiempo de ejecución. (Por cierto: es bueno señalar que es una probabilidad baja pero no nula).
Ventajas GC:
- un GC asigna simplemente incrementando un puntero, los asignadores de pila deben tomar contramedidas para evitar la fragmentación de pila
- un GC mejora la ubicación de la memoria caché de la CPU, un gran problema en los procesadores modernos
- un GC no requiere código adicional para liberar memoria, pocas probabilidades de fugas
- Un GG generacional puede recolectar basura al mismo tiempo, lo que hace que la administración de la memoria sea casi gratis en una CPU de múltiples núcleos.
Desventajas GC:
- difícil de hacer eficiente en un lenguaje que trata los punteros como tipos de primera clase
- usa más espacio de memoria virtual debido a la latencia de la colección
- abstracción con fugas para los recursos del sistema operativo distintos de la memoria
- puede causar pausas en la operación del programa en algunos casos mientras se recolecta la basura.
Es un slamdunk para el rendimiento, un GC vence un asignador de montón fácilmente sin esfuerzo. El concurso de programación del Diccionario Chino entre Rico Mariani y Raymond Chen se cita a menudo, el resumen está aquí . El programa C ++ de Raymond finalmente ganó, pero solo después de reescribirlo varias veces y renunciar a la biblioteca estándar de C ++.