c# garbage-collection finance

c# - ¿Cómo estas personas evitan la creación de basura?



garbage-collection finance (5)

Aquí hay un article interesante que encontré en la web.

Habla de cómo esta empresa puede analizar una gran cantidad de datos financieros en un entorno administrado, esencialmente mediante la reutilización de objetos y evitando inmutables como cadenas. Luego continúan y muestran que su programa no realiza ningún GC durante la fase de operación continua.

Esto es bastante impresionante, y me gustaría saber si alguien más aquí tiene algunas pautas más detalladas sobre cómo hacerlo. Por un lado, me pregunto cómo diablos puede evitar el uso de la cadena, cuando descaradamente algunos de los datos dentro de los mensajes son cadenas, y cualquiera que sea la aplicación cliente que vea los mensajes, ¿querrá que se les pasen esas cadenas? Además, ¿qué asigna en la fase de inicio? ¿Cómo sabrás que es suficiente? ¿Es una simple cuestión de reclamar una gran cantidad de memoria y mantener una referencia a ella para que GC no se active? ¿Qué pasa con cualquier aplicación cliente que esté usando los mensajes? ¿También debe escribirse de acuerdo con estos estrictos estándares?

Además, ¿necesitaría una herramienta especial para mirar la memoria? He estado usando el generador de perfiles de memoria SciTech hasta ahora.


En el 99% del tiempo, perderá dinero de sus jefes cuando intente lograrlo. El artículo describe un escenario extremo absoluto en el que necesitan la última caída de rendimiento. Como puede leer en el artículo, hay grandes partes del marco .NET que no se pueden usar cuando se intenta estar libre de GC. Algunas de las partes más básicas del BCL usan asignaciones de memoria (o ''producen basura'', como lo llama el papel). Tendrá que encontrar una manera de evitar esos métodos. E incluso cuando necesite aplicaciones increíblemente rápidas, es mejor que intente construir una aplicación / arquitectura que pueda escalar (usar varias máquinas), antes de intentar recorrer la ruta sin GC. La única razón para que usen la ruta sin GC es que necesitan una latencia baja absoluta. OMI, cuando necesita velocidad absoluta, pero no le importa el tiempo de respuesta mínimo absoluto, será difícil justificar una arquitectura sin GC. Además, si intenta crear una aplicación cliente libre de GC (como Windows Forms o WPF App); Olvídalo, esos marcos de presentación crean constantemente nuevos objetos.

Pero si realmente quieres esto, es bastante simple. Aquí hay un simple cómo:

  • Averigüe qué partes de la API .NET no se pueden usar (puede escribir una herramienta que analice los ensamblajes .NET utilizando un motor de introspección ).
  • Escriba un programa que verifique el código que usted o sus desarrolladores escriben para asegurarse de que no asignan directamente o utilizan métodos "prohibidos" .NET, utilizando la lista segura creada en el punto anterior (FxCop es una gran herramienta para esto).
  • Cree grupos de objetos que inicialice en el momento del inicio. El resto del programa puede reutilizar el objeto existente para que no tengan que hacer ninguna operación new .
  • Si necesita manipular cadenas, use matrices de bytes para esto y almacene matrices de bytes en un grupo (WCF usa esta técnica también). Tendrá que crear una API que permita manipular esas matrices de bytes.
  • Y por último, pero no menos importante, perfil, perfil, perfil.

Buena suerte


Encontré el papel al que está vinculado bastante deficiente:

  • Asume, y quiere que asumas, que la recolección de basura es el mejor asesino de latencia. No han explicado por qué piensan eso, ni han explicado de qué manera su sistema no es básicamente un recolector de basura a medida disfrazado.
  • Habla de la cantidad de memoria limpiada en la recolección de basura, lo cual es irrelevante: el tiempo que se tarda en recolectar basura depende más del número de objetos , independientemente de su tamaño.
  • La tabla de "resultados" en la parte inferior no ofrece comparación con un sistema que utiliza el recolector de basura de .NET.

Por supuesto, esto no significa que mienten y no tiene nada que ver con la recolección de basura, sino que básicamente significa que el papel solo intenta sonar impresionante sin divulgar nada útil que puedas usar para construir el tuyo.


Por lo que entendí, el artículo no dice que no usen cuerdas. No usan cuerdas inmutables . El problema con las cadenas inmutables es que cuando se analiza, la mayoría de las cadenas generadas son solo cadenas desechables.

Supongo que están usando algún tipo de asignación previa combinada con listas gratuitas de cadenas mutables.


Trabajé por un tiempo con un producto de CEP llamado StreamBase . Uno de sus ingenieros me dijo que estaban migrando su código C ++ a Java porque estaban obteniendo un mejor rendimiento, menos errores y una mejor portabilidad en la JVM al evitar por completo el GC. Me imagino que los argumentos se aplican también a la CLR.

Parecía contrario a la intuición, pero su producto era increíblemente rápido.

Aquí hay alguna información de su sitio :

StreamBase evita la recolección de basura de dos maneras: No usa objetos, y solo usa el conjunto mínimo de objetos que necesitamos.

Primero, evitamos el uso de objetos utilizando tipos primitivos de Java (Boolean, byte, int, double y long) para representar nuestros datos para su procesamiento. Cada tipo de datos de StreamBase está representado por uno o más tipos primitivos. Al manipular solo los tipos primitivos, podemos almacenar datos de manera eficiente en regiones de memoria asignadas a la pila o al arreglo. Luego podemos usar técnicas como matrices paralelas o métodos de llamada para pasar datos de manera eficiente.

Segundo, cuando usamos objetos, tenemos cuidado con su creación y destrucción. Tendemos a agrupar objetos en lugar de liberarlos para la recolección de basura. Intentamos administrar el ciclo de vida de los objetos de forma tal que los objetos sean capturados por el recolector de basura en la generación joven, o que se conserven para siempre.

Finalmente, probamos esto internamente utilizando un arnés de evaluación comparativa que mide la recolección de basura por tupla. Para lograr nuestras altas velocidades, intentamos eliminar toda la recolección de basura por tupla, generalmente con buen éxito.


Una cosa a tener en cuenta desde el principio es cuando dicen que "la sabiduría convencional ha estado desarrollando una tecnología de mensajería de baja latencia que requiere el uso de C ++ no administrado o lenguaje ensamblador". En particular, están hablando de un tipo de caso en el que las personas a menudo rechazan una solución .NET (o Java) de las manos. Por lo demás, una solución de C ++ relativamente ingenua probablemente tampoco lo haga.

Otra cosa a considerar aquí es que, en esencia, no se han deshecho tanto del GC como de su reemplazo, hay un código que administra la vida útil del objeto, pero es su propio código.

Hay varias maneras diferentes en que uno podría hacer esto en su lugar. Aquí hay uno. Digamos que necesito crear y destruir varios objetos Foo mientras mi aplicación se ejecuta. La creación de Foo está parametrizada por un int, por lo que el código normal sería:

public class Foo { private readonly int _bar; Foo(int bar) { _bar = bar; } /* other code that makes this class actually interesting. */ } public class UsesFoo { public void FooUsedHere(int param) { Foo baz = new Foo(param) //Do something here //baz falls out of scope and is liable to GC colleciton } }

Un enfoque muy diferente es:

public class Foo { private static readonly Foo[] FOO_STORE = new Foo[MOST_POSSIBLY_NEEDED]; private static Foo FREE; static Foo() { Foo last = FOO_STORE[MOST_POSSIBLY_NEEDED -1] = new Foo(); int idx = MOST_POSSIBLY_NEEDED - 1; while(idx != 0) { Foo newFoo = FOO_STORE[--idx] = new Foo(); newFoo._next = FOO_STORE[idx + 1]; } FREE = last._next = FOO_STORE[0]; } private Foo _next; //Note _bar is no longer readonly. We lose the advantages //as a cost of reusing objects. Even if Foo acts immutable //it isn''t really. private int _bar; public static Foo GetFoo(int bar) { Foo ret = FREE; FREE = ret._next; return ret; } public void Release() { _next = FREE; FREE = this; } /* other code that makes this class actually interesting. */ } public class UsesFoo { public void FooUsedHere(int param) { Foo baz = Foo.GetFoo(param) //Do something here baz.Release(); } }

Se pueden agregar más complicaciones si es multihilo (aunque para un rendimiento realmente alto en un entorno no interactivo, es posible que desee tener un hilo, o almacenes separados de clases de Foo por hilo), y si no puede predecir MOST_POSSIBLY_NEEDED por adelantado ( lo más sencillo es crear un nuevo Foo () según sea necesario, pero no liberarlo para GC, lo que puede hacerse fácilmente en el código anterior creando un nuevo Foo si FREE._next es nulo).

Si permitimos códigos no seguros, podemos tener ventajas aún mayores al tener una estructura Foo (y, por lo tanto, la matriz que contiene un área contigua de la memoria de la pila), _next es un puntero a Foo, y GetFoo () devuelve un puntero.

Si esto es lo que realmente están haciendo estas personas, por supuesto no puedo decirlo, pero lo anterior impide que se active el GC. Esto solo será más rápido en condiciones de rendimiento muy alto, de lo contrario, permitir que GC haga su trabajo es probablemente mejor (GC realmente te ayuda, a pesar del 90% de las preguntas sobre si lo trata como un Big Bad).

Hay otros enfoques que de manera similar evitan GC. En C ++, los operadores nuevos y eliminados pueden anularse, lo que permite que se modifiquen los comportamientos predeterminados de creación y destrucción, y las discusiones sobre cómo y por qué uno podría hacerlo podrían interesarle.

Una conclusión práctica de esto es cuando los objetos contienen recursos distintos de la memoria que son caros (por ejemplo, conexiones a bases de datos) o "aprenden" a medida que se siguen utilizando (por ejemplo, XmlNameTables). En este caso, agrupar objetos es útil (las conexiones ADO.NET lo hacen entre bambalinas de manera predeterminada). En este caso, aunque una cola simple es el camino a seguir, ya que la sobrecarga adicional en términos de memoria no importa. También puede abandonar objetos en disputa de bloqueo (está buscando obtener un rendimiento, y la contención de bloqueo le hará más daño que abandonar el objeto), lo cual dudo que funcione en su caso.