2010-08-07 14 views
16

Aquí hay un interesante article que encontré en la web.¿Cómo evitan estas personas la creación de basura?

Habla de cómo esta empresa es capaz de analizar una gran cantidad de datos financieros en un entorno administrado, esencialmente mediante la reutilización de objetos y evitando elementos inmutables como cadenas. Luego continúan y muestran que su programa no hace 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 hacer esto. Por un lado, me pregunto cómo diablos se puede evitar el uso de cadenas, cuando de manera indirecta algunos de los datos dentro de los mensajes son cadenas, y cualquiera que sea la aplicación del cliente que está mirando los mensajes, ¿querrá pasar esas cadenas? Además, ¿qué asignas en la fase de inicio? ¿Cómo sabrá que es suficiente? ¿Es simple una cuestión de reclamar una gran cantidad de memoria y mantener una referencia para que GC no entre? ¿Qué pasa con cualquier aplicación cliente que use los mensajes? ¿También se debe escribir de acuerdo con estos estándares estrictos?

Además, ¿necesitaría una herramienta especial para mirar la memoria? Hasta ahora he estado usando el perfilador de memoria SciTech.

+0

¿Realmente * necesita * para hacer esto? La mayoría de los sistemas casi en tiempo real simplemente usan código no administrado. –

+5

No, NO NECESITO hacerlo. Pero me encantaría saber cómo. – Carlos

Respuesta

9

he encontrado el papel se ha vinculado a más bien deficiente:

  • Se asume, y quiere que asume, que la recolección de basura es el último asesino de latencia. No han explicado por qué piensan así, ni han explicado de qué manera su sistema no es básicamente un recolector de basura hecho a medida disfrazado.
  • Se habla de la cantidad de memoria limpiado en la recolección de basura, la cual es irrelevante: el tiempo necesario para recoger la basura depende más de la cantidad de objetos , independientemente de su tamaño.
  • La tabla de "resultados" en la parte inferior no ofrece comparación con un sistema que usa el recolector de elementos no utilizados de .NET.

Por supuesto, esto no significa que estén mintiendo y no tiene nada que ver con la recolección de basura, pero básicamente significa que el papel está tratando de sonar impresionante sin divulgar nada útil que pueda usar para construye tu propio.

+0

¿Crees que este artículo es una pista falsa? Tenía una inclinación a pensar así cuando vi su razón para usar .NET ("MSFT gestiona los cambios de hardware"), que no es realmente un gran beneficio. – Carlos

+0

Administrar los cambios de hardware puede ser una gran ventaja con el tipo de rendimiento del que están hablando. En ese nivel, la gente querrá recompilar e incluso reescribir con nuevas optimizaciones del compilador para una nueva arquitectura, algo que optimizará JITting debería hacer por usted. –

+2

La mayoría de los JIT no hacen la optimización suficiente para competir con la compilación estática con la optimización guiada por perfil. La razón para usar .net es que es mucho más barato producir código administrado. Hacer algo como esto no es muy complicado. Usted asigna todos sus recursos por adelantado y luego no ejecuta el GC. Muchos implementan este tipo de arquitectura usando grupos de objetos. –

2

Según lo que he entendido, el artículo no dice que no usen cadenas. No usan cadenas inmutables. El problema con las cadenas inmutables es que cuando se hace un análisis sintáctico, la mayoría de las cadenas generadas son solo cadenas desechables.

Supongo que están utilizando algún tipo de preasignación combinada con free lists de cadenas mutables.

2

Trabajé durante 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 el GC por completo. Imagino que los argumentos se aplican también al CLR.

Parecía contra-intuitivo, pero su producto era increíblemente rápido.

He aquí alguna información from their site:

StreamBase evita la recolección de basura de dos maneras: no se utiliza objetos, y sólo con el conjunto mínimo de objetos que necesitamos.

En primer lugar, evitamos el uso de objetos mediante el uso de tipos de primitiva 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 solo manipular los tipos primitivos, podemos almacenar datos de manera eficiente en las regiones asignadas de memoria de la pila o matriz. A continuación, podemos utilizar técnicas como matrices en paralelo o llamadas a métodos para pasar datos de manera eficiente.

En segundo lugar, 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 modo que los objetos sean capturados por el recolector de basura en la generación joven o guardados 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, tratamos de eliminar toda la recolección de basura per-tuple, generalmente con buen éxito.

+2

Honestamente, odiaría trabajar en esa base de código por su sonido. Sin modelo de objeto, sin estructura de código, guau. Eso es horrible. Si tan desesperadamente querían evitar el GC, ¿por qué cambiar a Java en primer lugar? –

+0

Como dije, es contrario a la intuición. Sin embargo, tenían un gran producto con un gran rendimiento, desarrollado por algunas personas inteligentes. Supongo que tenían sus razones. Sin embargo, no es que no tuvieran un modelo de objeto ni una estructura de código. Es solo que reutilizan objetos siempre que sea posible y cuando se requiere GC, se aseguran de que el objeto esté en Gen0 (buena práctica de todos modos). No soy un gurú de C++, pero creo que prefiero programar C# que C++, incluso con las restricciones que ellos mismos establecieron. –

+1

Absolutamente. C++ tiene poca ventaja aquí y C# tiene las enormes ventajas de seguridad de memoria y .NET interoperabilidad. –

0

En el 99% del tiempo perderás el dinero de tus jefes cuando trates de lograrlo. El artículo describe un escenario extremo absoluto donde necesitan la última gota de rendimiento. Como puede leer en el artículo, hay grandes partes del framework .NET que no se pueden usar cuando se trata de estar libre de GC. Algunas de las partes más básicas del BCL usan asignaciones de memoria (o 'produce basura', como el papel lo llama). Necesitarás encontrar la manera de sortear esos métodos. E incluso cuando necesite aplicaciones absolutamente rápidas y absolutas, será mejor que primero 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 utilicen la ruta sin GC es que necesitan una latencia absolutamente baja. 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 de esto, si intentas construir una aplicación cliente libre de GC (como Windows Forms o la aplicación WPF); Olvídalo, esos marcos de presentación crean nuevos objetos constantemente.

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

  • Estudia qué partes de la API .NET no se pueden utilizar (se puede escribir una herramienta que analiza los ensamblados .NET utilizando un introspection engine).
  • Escriba un programa que verifique el código que usted o sus desarrolladores escriben para asegurarse de que no lo asignen directamente o utilicen métodos .NET "prohibidos", 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 objetos existentes para que no tengan que realizar operaciones new.
  • Si necesita manipular cadenas, use matrices de bytes para esto y almacene matrices de bytes en una agrupación (WCF también utiliza esta técnica). Deberá crear una API que permita manipular esas matrices de bytes.
  • Y por último pero no menos importante, perfil, perfil, perfil.

Buena suerte

5

Una cosa a destacar desde el principio es donde se dice que "la sabiduría convencional ha sido el desarrollo de la tecnología de baja latencia de mensajería requiere el uso de C++ no administrado o lenguaje ensamblador". En particular, están hablando de una especie de caso en el que las personas a menudo descartan una solución .NET (o Java) sin más. En ese caso, una solución de C++ relativamente ingenua probablemente tampoco lo haría.

Otra cosa a considerar aquí, es que esencialmente no se han deshecho tanto del GC como lo han reemplazado - hay código allí administrando la duración del objeto, pero es su propio código.

En su lugar, hay varias maneras diferentes de hacerlo. Aquí hay uno. Supongamos que necesito crear y destruir varios objetos Foo a medida que se ejecuta mi aplicación. creación foo está parametrizado 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(); 
    } 
} 

Además complicación puede ser añadido si usted está multiproceso (aunque de muy alto rendimiento en un entorno no interactivo, es posible que desee tener un hilo o tiendas separadas de clases Foo por hilo), y si no puede predecir MOST_POSSIBLY_NEEDED por adelantado (lo más simple es crear un nuevo Foo() según sea necesario, pero no liberarlo para GC, que se puede hacer fácilmente en el código anterior creando un nuevo Foo si FREE._next es nulo).

Si permitimos un código inseguro podemos tener ventajas aún mayores al tener Foo a struct (y por lo tanto la matriz que contiene un área contigua de la memoria de la pila), _siguiente puntero a Foo y GetFoo() devolviendo un puntero.

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

Hay otros enfoques que también evitan GC. En C++, los operadores nuevos y de eliminación pueden anularse, lo que permite cambiar el comportamiento predeterminado de creación y destrucción, y las discusiones sobre cómo y por qué uno podría serlo podrían interesarle.

Una práctica para llevar a cabo es cuando los objetos contienen recursos que no son de memoria que son caros (por ejemplo, conexiones a bases de datos) o "aprender" a medida que se utilizan (por ejemplo, XmlNameTables). En este caso, la agrupación de objetos es útil (las conexiones ADO.NET lo hacen de forma predeterminada). En este caso, aunque una cola simple es el camino a seguir, como la sobrecarga adicional en términos de memoria no importa. También puede abandonar objetos en contención de bloqueo (está buscando obtener rendimiento, y la contención de bloqueo lo dañará más que abandonar el objeto), lo que dudo que funcione en su caso.

+0

+1 por "dejar que GC haga sus cosas es probablemente mejor". – Steven

+0

Diablos, sí, aunque hay momentos en que cosas como esta son realmente útiles, la mayoría de los métodos para usurpar el GC caen en la categoría de "interesante, ahora nunca lo hagas" mientras que la mayoría de los intentos de usurparlo caen en la categoría de "tuviste" un problema, hiciste algo, ahora tienes dos problemas ". Solo una vez tuve motivos para hacer algo más que dejar que el GC hiciera lo suyo en código real, y esa vez era muy local en un lugar donde los patrones de uso de memoria de la aplicación fueron completamente diferentes a su operación normal. –

Cuestiones relacionadas