2009-01-07 14 views
7

En mi informe de perfilo cada vez veo más los resultados de las pruebas simuladas con inyección de dependencia. Muchas de las dependencias eran estáticas, sino porque queremos poner a prueba los métodos de aislamiento en el que se cambian a los miembros de instancia, como el siguiente ejemplo:Problemas de rendimiento de la inyección de dependencia

class ShortLivedThing { 
    IDependency1 dep1; 
    IDependency1 dep2; 
    IDependency1 dep3; 
    ... 

    int TheRealData; 

    // Constructor used in production 
    public ShortLivedThing() { 
    dep1 = new Dep1(); dep2 = new Dep2(); dep3 = new Dep3(); 
    } 

    // DI for testing 
    public ShortLivedThing(IDependency1 d1, IDependency2 d2, IDependency3 d3) { 
    dep1 = d1(); dep2 = d2(); dep3 = d3(); 
    } 
} 

A su vez las dependencias de la mayoría de las veces tienen otras dependencias y así sucesivamente. Esto da como resultado la creación de un árbol de objetos (en su mayoría "estáticos") cada vez que se realiza una llamada al método fuera de las pruebas. Cada uno de los objetos es muy pequeño (solo unos pocos punteros), pero el efecto de árbol lo convierte en un golpe de rendimiento cada vez mayor.

¿Qué podemos hacer al respecto?

+0

¿Está perfilando su código de producción o sus pruebas? –

+0

Estoy perfilando el código de producción. Las pruebas se ejecutan rápidamente porque no crean las dependencias de las dependencias. –

Respuesta

8

Me parece que necesita aprovechar las características que un marco de inyección de dependencia adecuado puede proporcionarle. No use lógica de construcción diferente para pruebas/producción.

Con muelle, las inyecciones de singleton solo se realizan al iniciar el recipiente. Las inyecciones de prototipos se hacen todo el tiempo. El cableado completo también se realiza cada vez que ejecuta una prueba de unidad, si se está cableando. Por lo tanto, las pruebas unitarias de perfil generalmente no son una buena idea.

¿Tal vez está usando muy poco de los ámbitos de singleton y demasiado alcance de prototipo? (Prototipo = nueva instancia cada vez)

Lo bueno de la inyección de primavera es que se puede usar proxies alcance, es decir, su gráfico de objetos puede tener este aspecto:

A Singleton 
| 
B Singleton 
| 
C Prototype (per-invocation) 
| 
D Singleton 
| 
E Session scope (web app) 
| 
F Singleton 

Y cada solicitud sólo se crearía 1 instancia de C y una instancia de E por sesión. A, B, D y F son singletons. Si no es una aplicación web, no tiene el alcance de la sesión de forma predeterminada, pero también puede crear ámbitos personalizados (un alcance de "Ventana" parece genial para una aplicación de escritorio con ventana). La clave aquí es que puede "introducir" ámbitos en cualquier nivel, efectivamente puede tener diez capas de objetos únicos y, de repente, aparece algo con una sesión en el alcance. (Esto realmente puede revolucionar la forma de implementar algunas características transversales en una arquitectura en capas, pero esa es una historia diferente)

Esto realmente da la creación de objeto mínimo posible dentro de un modelo DI, creo.

Aunque esto es Spring for Java, creo que varios otros marcos DI deberían soportar características similares. Quizás no sean los más minimalistas.

+0

¿Se le ha dado F al objeto E? Si es así, ¿cómo funciona esto con múltiples usuarios? –

+0

Estaba pensando de arriba a abajo, y a D se le da el E. Proxies dinámicos o cglib hace la magia. – krosenvold

+0

Y sí, funciona muy bien con múltiples usuarios. 300 caracteres se vuelven levemente pequeños Consulte http://static.springframework.org/spring/docs/2.5.x/reference/beans.html#beans-factory-scopes – krosenvold

1

¿Qué le parece pasar referencias?

0

Lo mejor que se me ocurre es poner todas las dependencias en un solo objeto de "contexto", que luego se comparte entre todas las instancias. Esto debería mitigar un poco el problema de rendimiento.

1

Si su preocupación son las pruebas lentas, intente ejecutarlas en paralelo y no permita que el proceso de prueba interrumpa a sus programadores.

automatizar este proceso:

  • Cuando alguien comprueba en, crea una acumulación del repositorio.
  • Ejecute pruebas en esta compilación.
  • Envíe los resultados por correo electrónico al desarrollador que realizó la facturación.

Es mejor si la primera verificación no se realiza en el repositorio real. Hazlo de forma temporal y crea la estructura de esto. Opcionalmente puede realizar pruebas de rendimiento, controles de estilo, etc. e incluirlos en el correo electrónico. Si hace esto, agregue un paso al proceso automatizado:

  • Si las pruebas pasan (y se cumplen los criterios opcionales), combine el nuevo código con el repositorio real.

De esta manera, las pruebas lentas no son una preocupación. Además, cuando un desarrollador necesita saber si su código rompió algo o hizo el aumento de rendimiento que esperaba, simplemente se registra y espera el correo electrónico generado para ella.

2

Creo que solo deberías tener el "constructor DI". Usted llama a este constructor para probarlo, así como en producción.

class ShortLivedThing { 
    IDependency1 dep1; 
    IDependency1 dep2; 
    IDependency1 dep3; 
    ... 

    int TheRealData; 

    public ShortLivedThing(IDependency1 d1, IDependency2 d2, IDependency3 d3) { 
    dep1 = d1; dep2 = d2; dep3 = d3; 
    } 
} 

De esta manera usted no tiene el problema de crear instancias de un árbol de objetos cada vez que una llamada al método se realiza fuera de sus pruebas. Por supuesto, para la producción debe conectar correctamente sus objetos fuera de los objetos participantes, lo que es bueno.

En resumen: no vayas por el 50% DI/50% de codificación rígida, ve por 100% DI.

0

Si tiene como objetivo .NET, consulte Autofac. Tiene varios ámbitos (singleton, fábrica, contenedor) para modificar los aspectos de creación, eliminación determinista para mantener a raya el uso de recursos y permite usar GeneratedFactories y expresiones lambda para configurar componentes y evitar el costo de la reflexión.

Cuestiones relacionadas