调用函数两次,而不是存储输出并在Java中使用

调用函数两次,而不是存储输出并在Java中使用,java,performance,function-calls,Java,Performance,Function Calls,假设我有一个boolean函数isCorrect(Set) 该函数的参数由另一个函数buildSet()计算 在时间和空间效率方面,哪一个更好 Set<Integer> set = buildSet(); if(isCorrect(set)) doSomethingWith(set); 第一种方法更好,我不认为这是一个意见问题。当你已经有结果时,不要浪费两次调用同一个函数。当然,我假设buildSet()没有任何必要的副作用 在时间和空间效率方面,哪一个更好 Set<

假设我有一个
boolean
函数
isCorrect(Set)

该函数的参数由另一个函数
buildSet()
计算

在时间和空间效率方面,哪一个更好

Set<Integer> set = buildSet();
if(isCorrect(set))
    doSomethingWith(set);

第一种方法更好,我不认为这是一个意见问题。当你已经有结果时,不要浪费两次调用同一个函数。当然,我假设
buildSet()
没有任何必要的副作用

在时间和空间效率方面,哪一个更好

Set<Integer> set = buildSet();
if(isCorrect(set))
    doSomethingWith(set);

就时间而言,您在第一个代码段中构建了一次集合,在第二个代码段中构建了两次集合,因此第二个代码段可能需要更长的时间。就空间而言,可能不会有什么不同。然而,您似乎在第二个代码段中实例化了两个对象,在第一个代码段中只实例化了一个对象(同样,我不能确定这一点,因为我不知道如何实现
buildSet()
)。如果是这种情况,并且您保留了这两个对象,那么第二个代码段将使用两倍的空间。

答案是-这取决于。见下文:

  • 如果函数调用很费时,则必须存储结果
  • 存储结果可能会使代码的可读性降低一点,尽管可读性不是一个客观指标
  • 有时您可能需要确定,第一个和第二个用法处理的对象是完全相同的。当您两次调用函数时,可能会得到不同的结果。这种情况通常称为竞争条件或数据竞争,在大多数情况下,它会影响程序的正确性

因此,总结一下:在大多数情况下,存储结果是有意义的。有时(但我不太经常)这并不是真的必要。

虽然现有的答案给出了存储值是最好的理由,但它们忽略了我认为最重要的一点(事实上,最重要的一点):在第二个示例中(运行函数两次),您引入了一个潜在的竞争条件

如果
buildSet()
依赖于外部因素(这在任何非平凡函数中都是非常可能的,并且在以后的更改中可能会变为真),则值在
If
检查和第二次调用之间可能会发生变化。这可能会产生一个微妙的、难以发现的bug,只有当您在其他地方做了更改,或者某些事件在特定的时间发生时,它才可能变得可见


这本身就是避免这种模式的一个很好的理由。

通过从第二个示例(两个调用)转到第一个示例(一个调用),您将节省对
buildSet
的第二个调用在堆栈上的时间。如果该调用有10%的时间在堆栈上,那么您的加速比将是100/90=1.11或11%。如果50%的时间都在堆栈上,则加速比将为100/50=2或100%

如何知道函数调用在堆栈上的时间占多大比例?
这包括挂钟的百分比

并不是每个剖析者都会告诉你这一点

  • 如果它只告诉您“self time”,而不是inclusive time,那么它不会告诉您函数调用所花费的时间
  • 如果它只通过函数而不是行来告诉您,那么您就无法判断第二个调用是否是问题所在(与第一个调用相反)
  • 如果它只是一个“CPU探查器”,那么如果在
    buildSet
    函数或应用程序中的其他地方存在任何I/O、睡眠或锁定等待,探查器将表现为不存在
  • 如果它不告诉你百分比,而只告诉你毫秒或呼叫计数,那么你必须计算出呼叫中总时间的百分比
  • 呼叫图不会告诉你,“火焰图”不会告诉你,时间线不会告诉你,等等
能告诉你的是。 如果你能想出如何告诉他们该做什么,其他人可能会这么做。
我和许多人使用的方法是。

事实上,我发现第一个代码段更可读,但可能只有我一个人。@arshajii这就是为什么我写它是非常主观的:)顺便说一句,我同意你对那个特定代码段的看法。如果你发现后一个代码段更可读,那么通过正确命名局部变量可能可以保持可读性(例如:
builtSet
——用一个更现实的例子,当然会更好)。如果只引用一次值,那么没有区别——它们将生成基本相同的字节码,执行速度也一样快。如果需要第二次引用值,那么第一次从技术上讲是“更好的”,但有时代码更清晰,例如第二个(或有时第一个更清晰)。恐怕您必须进行判断。重要的一点是,第一种方法更容易测试,因为
buildSet
的结果可以在调试器中轻松检查。因此,如果检索的值不可合理预测或调用的方法不可预测,则我倾向于选择第一种方法,即使对于单个访问也是如此没有经过很好的测试。如果
buildSet
只是一个getter,那么它很可能由JITC内联,没有区别。当然,如果它确实构造了集合,那么应该避免重复调用,因为创建一个对象通常非常昂贵。@HotLicks If
buildSet()
是一个getter,它的命名非常糟糕(甚至有误导性)。@HotLicks是的,尽管想必
buildSet()
正在进行一些“构建”.@Lattyware-这将是SO中出现的第一个选择不当的名称。@HotLicks我不是说它不值得一提,只是如果用户在这种情况下,他们应该相应地命名他们的方法。