【问题标题】:Can the java compiler optimize loops to return early?java编译器可以优化循环以提前返回吗?
【发布时间】:2018-12-14 22:24:47
【问题描述】:

我正在与一个决定自行处理集合的外部库合作。不使用它或更新不在我的控制范围内。要使用这个第三方“集合”的元素,它只返回迭代器。

在代码审查期间提出了一个问题,即在代码中使用多个返回以获得性能。我们都同意(在团队内部)单次返回代码更具可读性,但有些人担心优化。

我知道过早的优化是不好的。这是另一天的话题。

我相信 JIT 编译器可以处理这个问题并跳过不需要的迭代,但找不到任何信息来支持它。 JIT 有这种能力吗?

手头问题的代码示例:

public void boolean contains(MyThings things, String valueToFind) {
    Iterator<Thing> thingIterator = things.iterator();
    boolean valueFound = false;
    while(thingIterator.hasNext()) {
        Thing thing = thingIterator.next();
        if (valueToFind.equals(thing.getValue())) {
            valueFound = true;
        }
    }
    return valueFound;
}

VS

public void boolean contains(MyThings things, String valueToFind) {
    Iterator<Thing> thingIterator = things.iterator();
    while(thingIterator.hasNext()) {
        Thing thing = thingIterator.next();
        if (valueToFind.equals(thing.getValue())) {
            return true;
        }
    }
    return false;
}

【问题讨论】:

  • 为什么不在while 循环中使用break?但我很确定编译器将不会在第一个示例的早期终止循环。它如何知道不再需要以下迭代?
  • 这两个示例甚至没有相同的行为。第一个将返回集合的最后一个元素是否等于valueToFind,而第二个将返回valueToFind 是否在集合中的任何位置。
  • 是的,在示例代码中可以看出,中断会删除不必要的迭代。
  • 我怀疑它会这样做。 valueFound = valueToFind.equals(thing.getValue()); 有潜在的副作用,编译器会尽量避免抑制。
  • 我非常怀疑 JIT 编译器会做这样的事情,因为它是根据方法和方法被调用的次数来编译的。

标签: java performance jit


【解决方案1】:

我们都同意单次返回代码更具可读性。

不是真的。这只是老式的结构化编程,当时函数通常不会保持较小,并且保持值不可变的范例还不流行。

尽管存在争议,但使用在不同点返回的非常小的方法(几行代码)并没有错。例如,在递归方法中,通常至少有一个基本情况会立即返回,另一种基本情况会返回递归调用返回的值。

通常你会发现创建一个额外的结果变量,只是为了保存返回值,然后确保函数的其他部分没有覆盖结果,当你已经知道你可以返回时,只会产生噪音不那么可读而不是更多。读者必须处理认知超负荷才能看到结果不会进一步向下修改。在调试过程中,这会更加痛苦。

我不认为你的例子是过早的优化。它是搜索算法的逻辑和关键部分。这就是为什么你可以从循环中break,或者在你的情况下,只返回值。我认为 JIT 不会轻易意识到它应该打破循环。如果您在集合中找到其他内容,它不知道您是否要将变量更改回false。 (我认为意识到valueFound 不会变回false 并不聪明)。

在我看来,您的第二个示例不仅更具可读性(valueFound 变量只是额外的噪音)而且速度更快,因为它只是在完成工作时返回。如果您在设置valueFound = true 之后放置break,第一个示例将同样快。如果您不这样做,并且您有一百万个项目要检查,而您需要的项目是第一个,那么您将徒劳地比较所有其他项目。

【讨论】:

  • 编辑了问题以澄清协议是在与图书馆合作的团队内部。
  • @PaulDaPigeon 当然,只是说你团队中的这个协议可能已经过时了。
【解决方案2】:

Java 编译器不能做这样的优化,因为在一般情况下这样做会改变程序的逻辑。

具体来说,添加提前返回会改变thingIterator.hasNext() 的调用次数,因为您的第一个代码块会继续迭代集合直到结束。

Java 可能会用提前返回替换break,但这会对程序的时间产生任何影响。

【讨论】:

    猜你喜欢
    • 2017-11-05
    • 2017-02-05
    • 1970-01-01
    • 1970-01-01
    • 2012-12-29
    • 1970-01-01
    • 1970-01-01
    • 2016-07-21
    • 1970-01-01
    相关资源
    最近更新 更多