【问题标题】:Clean code vs performance干净的代码与性能
【发布时间】:2018-08-26 08:19:01
【问题描述】:

clean code的一些原则是:

  • 函数应该在一个抽象级别做一件事
  • 函数的长度最多为 20 行
  • 函数的输入参数不得超过 2 个

在 Java 中添加一个额外的函数调用会“丢失”多少个 cpu 周期?
是否有可用的编译器选项将许多小函数转换为一个大函数以优化性能?

例如

void foo() {
  bar1()
  bar2()
}

void bar1() {
  a();
  b();
}
void bar2() {
  c();
  d();
}

会变成

void foo() {
  a();
  b();
  c();
  d();
}

【问题讨论】:

  • Java 编译器相当“笨”。 JVM 将在运行时根据它运行的机器、代码的运行时特性(例如,哪个代码是“热的”等)和其他因素进行优化。不确定这是否以及如何适用于 Android。
  • 一般的经验法则是编写可读的代码(当需要修复时,可能会节省数百小时的开发人员时间)并且如果,仅当,分析器说[code-in-question] 做 [thing] 的时间太长,然后你优化 [thing]

标签: java android compiler-optimization


【解决方案1】:

在 Java 中添加一个额外的函数调用会“丢失”多少个 cpu 周期?

这取决于它是否内联。如果它是内联的,它将什么都不是(或名义金额)

如果它不是在运行时编译,它几乎不重要,因为干预的成本比微优化更重要,而且它可能没有被调用到足够重要(这就是它没有被优化的原因)

真正重要的唯一时间是代码经常被调用,但由于某种原因,它被阻止优化。我只假设是这种情况,因为您有一个分析器告诉您这是一个性能问题,在这种情况下,手动内联可能是答案。

我在 Java 中设计、开发和优化对延迟敏感的代码,我选择手动内联方法的时间远少于 1%,但只能在探查器(例如Flight Recorder 表明存在严重的性能问题。

在罕见的情况下,它有多大的不同?

我估计,在实际应用中,每次额外调用需要 0.03 到 0.1 微秒,在微基准测试中,它会少得多。

是否有可用的编译器选项将许多小函数转换为一个大函数以优化性能?

是的,实际上可能发生的情况不仅是所有这些方法都是内联的,而且调用它们的方法也是内联的,并且它们在运行时都不重要,但前提是代码被调用到足以进行优化。即不仅abcd 是内联的及其代码,而且foo 也是内联的。

默认Oracle JVM 可以行到 9 层的深度(直到代码获得超过 325 字节的字节码)

清理代码有助于提高性能

JVM 运行时优化器具有它优化的通用模式。干净、简单的代码通常更容易优化,当你尝试一些棘手或不明显的事情时,你最终可能会慢得多。如果对人类来说更难理解,那么优化者很可能很难理解/优化。

【讨论】:

    【解决方案2】:

    代码的运行时行为和清洁度(代码的编译时间或生命周期属性)属于不同的需求类别。在某些情况下,针对一个类别进行优化会损害另一个类别。

    问题是:哪个类别真正需要你关注?

    在我看来,代码的简洁性(或软件的延展性)严重缺乏关注。你应该首先关注这一点。只有当其他要求开始落后(例如性能)时,您才会询问这是否是由于代码的干净程度。这意味着您需要真正进行比较,您需要衡量它所产生的差异。关于性能,使用您选择的分析器:运行“脏”代码变体和干净变体并检查差异。是不是很明显?只有当“脏”变体明显更快时,您才应该降低清洁度。

    【讨论】:

      【解决方案3】:

      考虑以下代码,它将在一个 for 循环中执行 3 件事的代码与为每个任务具有 3 个不同 for 循环的另一个代码进行比较。

        @Test
        public void singleLoopVsMultiple() {
          for (int j = 0; j < 5; j++) {
      
            //single loop
            int x = 0, y = 0, z = 0;
            long l = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
              x++;
              y++;
              z++;
            }
            l = System.currentTimeMillis() - l;
      
      
            //multiple loops doing the same thing
            int a = 0, b = 0, c = 0;
            long m = System.currentTimeMillis();
            for (int i = 0; i < 100000000; i++) {
              a++;
            }
            for (int i = 0; i < 100000000; i++) {
              b++;
            }
            for (int i = 0; i < 100000000; i++) {
              c++;
            }
            m = System.currentTimeMillis() - m;
            System.out.println(String.format("%d,%d", l, m));
      
          }
        }
      

      当我运行它时,这是我得到的时间输出(以毫秒为单位)。

      6,5
      8,0
      0,0
      0,0
      0,0
      

      经过几次运行后,JVM 能够识别密集代码的热点并优化部分代码以显着提高它们的速度。在我们之前的示例中,经过 2 次运行后,JVM 已经对代码进行了大量优化,以至于围绕 for 循环的讨论变得多余了。

      除非我们知道内部发生了什么,否则我们无法预测引入 for 循环等变化对性能的影响。真正提高系统性能的唯一方法是对其进行测量并只关注解决实际瓶颈。

      清理代码可能会使 JVM 更快。但即使情况并非如此,每次性能优化都会增加代码复杂性。问问自己增加的复杂性是否值得未来的维护工作。毕竟,任何团队中最昂贵的资源是开发人员,而不是服务器,任何额外的复杂性都会减慢开发人员的速度,增加项目成本。

      处理它的方法是弄清楚你的基准,你正在制作什么样的应用程序,什么是瓶颈。如果您正在制作一个网络应用程序,也许数据库占用了大部分时间,减少函数的数量不会有什么不同。另一方面,如果它是一个运行在以性能为重的系统上的应用程序,那么每一件小事都很重要。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-10-28
        • 2010-11-13
        • 1970-01-01
        • 2013-08-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多