【问题标题】:Readability vs Performance可读性与性能
【发布时间】:2013-10-31 14:33:33
【问题描述】:

最近我们在工作中讨论了局部变量对 Java 代码的性能和可读性的影响。我的一些同事认为这样的声明

new DoSomethingCmd(new SelectionContext(context, keys), infoStuff.getCurrentRole().getRole_id()).execute(getResultContainer());

会给应用程序带来相当大的性能提升。他们愿意为此牺牲代码的可读性。他们声称这一点是对的吗?上面的版本是否比这个版本的性能要好得多?

final SelectionContext selectionContext = new SelectionContext(context, keys);
final String roleId = infoStuff.getCurrentRole().getRole_id();
final DeleteSomethingCmd deleteSomethingCmd = new DeleteSomethingCmd(selectionContext,roleId);
deleteSomethingCmd.execute(getResultContainer());

我意识到第一个语句本身并不难掌握,但是当您的大部分代码都采用这种结构时,复杂性会很快增加。

感谢您的意见。

【问题讨论】:

  • 如果我理解了右上角的代码,您的同事似乎认为创建引用变量比创建匿名变量要慢。是对的吗?他们为什么这么认为?
  • 我很高兴将这些优化留给内置优化器。他们擅长那个。更好的是我们查看的源代码易于理解。
  • 除非您另有测量,否则您应该为清晰和简单而编写代码。这通常也足够快。当你已经衡量了一个性能问题时才考虑替代方案。通常,性能优化无济于事,只会增加复杂性。

标签: java performance code-readability


【解决方案1】:

“优化”版本所做的唯一事情是堆栈中的变量更少,从而略微增加了内存消耗。应该仔细衡量性能(谷歌如何对问题进行基准测试),但我严重怀疑它是否有任何明显的影响。

另外,花时间在一段不经常使用的代码上提高性能只会浪费开发人员的时间,而且成本很高。

在这种情况下,可读性应该占上风。

编辑:无论如何,如果您使用适当的缩进,我认为这两个版本在可读性方面差别太大:

new DoSomethingCmd(
    new SelectionContext(context, keys),
    infoStuff.getCurrentRole().getRole_id()
    ).execute(getResultContainer());

此文本的优点是您没有定义不再需要的变量(selectionContextroleId)(因此当您再次阅读该方法时,它们不会与更多“持久”变量混合)。无论如何,这是可以解释的;最重要的是,除非您有动机,否则您不应该担心优化。

除此之外,还有一些 Java 编程指南可以为您提供真正有用的技巧,真正帮助您(例如,使用 StringBuilder 连接字符串)。

【讨论】:

  • 由于堆栈空间是预先分配的,我不会认为堆栈上的一些变量会增加内存消耗。在需要额外的内存块之前,您将需要数千个额外的变量。
  • 我的回答的一个问题是它假定一个“哑”编译器将为每个变量提供一个固定的、不同的位置。一个优化的编译器会发现roleId 不再被使用,并为deleteSomethingCmd 重新使用它的内存位置,从而减少即使是很小的好处。同样,应使用基准检查优化以验证实际改进。
  • 好吧,javac 可能会在堆栈帧中为它们保留插槽,但这只是堆栈中的几个字节,由 JVM 预先分配一个大小几百kB。 oracle.com/technetwork/java/hotspotfaq-138619.html 说“从 Java SE 6 开始,这个值在 32 位 VM 中为 320k,在 64 位 VM 中为 1024k。” “64k 是每个线程允许的最少堆栈空间。”所以不管我们是多还是少使用几个字节,它们都是分配的
【解决方案2】:

他们声称这是正确的吗?

不,他们不是。将某些内容存储在局部变量中然后随后从中读取的成本接近于 0,就这么简单——这绝对不是你应该花时间优化甚至担心的事情关于那件事。不要通过将所有内容放在一个高度复杂的行中来牺牲可读性。

【讨论】:

    【解决方案3】:

    一个人的时间比电脑的时间贵几百万倍。

    如果预计代码行的执行次数比程序员看到的要多几百万倍,那么也许您应该考虑进行优化。但肯定不会在那之前。

    即便如此,把编译器当成笨蛋来优化也是完全没有意义的。

    别忘了! Premature optimization is the root of all evil.

    【讨论】:

      【解决方案4】:

      我在这里看到了几个函数调用

      • 两次调用new

      • SelectionContext 构造函数

      • 致电getRole_id()

      • DeleteSomethingCmd 构造函数

      • 致电GetResultContainer()

      • 调用deleteSomethingCmdexecute()成员

      所以假设你运行了无数次,并且在那段时间你获取了一些堆栈样本。

      您认为堆栈样本没有显示您在其中一个函数调用中的概率是多少?

      非常低,对吧?可能远低于百分之一?

      那么即使你在这个级别无限快速地编写代码,它会为你节省什么?

      观点就是一切。

      【讨论】:

        【解决方案5】:

        第一个版本比第二个版本性能要好得多。我怀疑您能否可靠地衡量性能差异。而且经过jit优化后,结果机器码应该是一样的。

        【讨论】:

          【解决方案6】:

          严格来说这些示例,不,两者之间没有可衡量的差异(无论如何,在现代系统上都没有)。但是,您确实可以通过编写大量较低级别的代码来完成可以在较高级别使用较少代码完成的工作,从而显着提高性能。例如,在许多情况下,使用数组比使用任何 Collections API 都要快(也许不多,但确实如此)。但是这样做不仅会失去可读性,还会失去可维护性,而且通常只会对性能产生微不足道的提升。

          代码应始终井井有条且易于阅读。只有在获得良好的性能指标后,您才应该考虑放弃它以提高性能。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-04-30
            • 2014-11-22
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-07-08
            • 2016-05-16
            • 1970-01-01
            相关资源
            最近更新 更多