【问题标题】:Performance problems from dependency injection依赖注入带来的性能问题
【发布时间】:2010-09-30 01:29:01
【问题描述】:

在我的分析器报告中,我越来越多地看到使用依赖注入进行基于模拟的测试的结果。许多依赖项是静态的,但因为我们想单独测试方法,所以将它们更改为实例成员,如下例所示:

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();
   }
}

反过来,大多数时候依赖项还有其他依赖项等等。这会导致实例化(主要是“静态”)对象树每次在测试之外进行方法调用。每个对象都非常小(只有几个指针),但树效应将其转化为不断增加的性能损失。

我们能做些什么呢?

【问题讨论】:

  • 你是在分析你的生产代码还是你的测试?
  • 我正在分析生产代码。测试运行速度很快,因为它们不构建依赖项的依赖项。

标签: dependency-injection


【解决方案1】:

在我看来,您需要利用适当的依赖注入框架可以为您提供的功能。不要使用不同的构造逻辑进行测试/生产。

使用 spring,单例注入仅在容器启动时执行。每次都进行原型注射。每次运行单元测试时,也会完成完整的接线,如果它正在接线的话。所以分析单元测试通常不是一个好主意。

也许您使用的单例范围太少而原型范围太多? (原型 = 每次都有新实例)

spring 注入的好处是您可以使用范围代理,这意味着您的对象图可以如下所示:

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

每个请求只会在每个会话中创建一个 C 实例和一个 E 实例。 A、B、D 和 F 是单例。如果它不是 web 应用程序,则默认情况下您没有会话范围,但您也可以制作自定义范围(“窗口”范围对于窗口桌面应用程序来说听起来很酷)。这里的线索是您可以在任何级别“引入”范围,有效地您可以拥有十层单例对象,并且突然之间会出现一些会话范围。 (这确实可以彻底改变您在分层架构中实现某些横切功能的方式,但这是另一回事)

我认为,这确实可以在 DI 模型中尽可能少地创建对象。

虽然这是 Java 的 Spring,但我相信许多其他 DI 框架应该支持类似的功能。也许不是最简约的。

【讨论】:

  • F 是否给定了 E 对象?如果是这样,这如何与多个用户一起使用?
  • 我从头到尾都在考虑,D 被赋予了 E。动态代理或 cglib 可以做到这一点。
  • 是的,它适用于多个用户。 300 个字符变得有点小。查看static.springframework.org/spring/docs/2.5.x/reference/…
【解决方案2】:

我认为你应该只有“DI 构造函数”。您可以在测试和生产环境中调用此构造函数。

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

   int TheRealData;

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

这样,每次在测试之外进行方法调用时,您就不会遇到实例化对象树的问题。当然,对于生产,您必须在参与对象本身外部正确连接对象,这是一件好事。

总而言之:不要追求 50% DI / 50% 硬编码,而要追求 100% DI。

【讨论】:

    【解决方案3】:

    如果您担心测试速度较慢,请尝试并行运行它们,不要让测试过程中断您的程序员。

    自动化这个过程:

    • 当有人签入时,构建存储库。
    • 在此版本上运行测试。
    • E - 将结果邮寄给签到的开发人员。

    最好不要对实际存储库进行首次签入。把它变成一个临时的,并以此为基础进行构建。您可以选择执行性能测试、样式检查等,并将这些内容包含在电子邮件中。如果您这样做,请在自动化流程中添加一个步骤:

    • 如果测试通过(并且满足可选条件),请将新代码与实际存储库合并。

    通过这种方式,缓慢的测试不再是问题。此外,当开发人员需要知道她的代码是否破坏了某些东西或实现了预期的性能提升时,她只需签入并等待为她生成的电子邮件。

    【讨论】:

      【解决方案4】:

      传递引用怎么样?

      【讨论】:

        【解决方案5】:

        我能想到的最好办法是将所有依赖项放入一个“上下文”对象中,然后在所有实例之间共享。这应该会在一定程度上缓解性能问题。

        【讨论】:

          【解决方案6】:

          如果您的目标是 .NET,请查看 Autofac。它具有各种范围(单例、工厂、容器)来调整创建方面、确定性处置以控制资源使用,并允许使用 GeneratedFactories 和 lambda 表达式来配​​置组件并避免反射成本。

          【讨论】:

            猜你喜欢
            • 2012-04-25
            • 2015-11-21
            • 2016-05-18
            • 2014-12-27
            • 2015-04-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多