-1

有时在算法的过程中,我们需要计算或简单地存储几个相互依赖或彼此分开没有任何意义的值。

就像一个(非常荒谬,但重要的是,简单)示例让我们在 anint[]中找到最接近 number的两个不同值3

int a = values[0];
int b = values[1];
for (int value : values) {
    int distance = Math.abs(value-3);
    if (value != b) {
        if (distance < Math.abs(a-3) ) {
            a = value;
        }
    }
    if (value != a) {
        if (distance < Math.abs(b-3) ) {
            b = value;
        }
    }        
}
takeSausageSliceBetween(a, b);

我们不能只在同一个类中创建方法computeA()computeB()因为计算ab是相互依赖的。所以计算a并且b只是要求重构为一个单独的类:

class DistinctNumbersNearestTo3 {
    final int a;
    final int b;
    DistinctNumbersNearestTo3(int[] values) {
        // same algorithm
    }
}

因此更高级别的代码变得清晰明了:

DistinctNumbersNearestTo3 nearest = new DistinctNumbersNearestTo3(values);
takeSausageSliceBetween(nearest.a, nearest.b);
// nearest never leaves the method it was declared in

但是(如果我错了,请纠正我)它引入了,至少对于某些级别的优化,一个实例化,然后是一个新对象的垃圾收集(只是来自伊甸园,但无论如何)只是为了清楚你的代码。并且在堆栈中寻找 int 也会变成在堆中寻找对象。

这里的问题是: JVM 是否足够聪明,最终可以优化重构的代码以像未重构的代码一样运行?

我想将几个变量组合成一个新对象只是为了更清晰的代码的另一种情况是,当我想a()通过引入一个新类来将一个长方法重构为几个新方法时,该类C包含特定于a()'s 调用的数据,因此包含那些对这些数据进行操作的几种新方法。我在实现一个表示一组链的类时偶然发现了这种情况,我们可以向其中添加新的链接(节点对),从而扩展现有的链,将两条链组合在一起,或者创建一个包含两个节点的新链。让我们仅出于某种观点讨论该特定类:

public class SetOfChains {
    List<List<Node>> chains;
    public void addLink(Node a, Node b) {
        // Very long method that mutates field this.chains. Needs refactoring.
    }
}

要正确拆分addLink为 的几个方法SetOfChains,这些新方法中的每一个都必须有很多参数,因为addLink()addLink(). 将该数据保存到的专用字段SetOfChains也是可能的,但有异味且难以表达(因为它仅在方法调用期间才有意义),因此显然我创建了一个新的内部类来将该数据保存在其字段中并执行所有算法步骤,chains像未重构的那样改变外部字段addLink()

class SetOfChains{
    List<List<Node>> chains;
    void addLink(Node a, Node b) {
      new LinkAddition(a,b).compute();
    }
    class LinkAddiditon { // Instances never leave addLink()'s scope
        private final Something bunch, of, common, fields;
        compute() {
            this.properly();
            this.isolated();
            this.steps();
            this.of();
            this.the();
            this.refactored();
            this.method();
        }
        // There be code for the properly isolated steps...
    }

但是由于在功能上这相当于让我的旧 long unrefactored addLink(),JVM 是否可以使生成的指令与未重构情况下一样优化,就好像没有任何LinkAddition对象一样?

因此,要在这堵文字墙下画出底线:如果我通过将数据和方法提取到新而不是方法来争取易于理解的代码,是否必然会导致性能下降?

我是否正确理解所描述的案例正是逃逸分析的内容?

4

1 回答 1

2

有人说“过早的优化是万恶之源”。

一般来说,最好让代码更易于理解,并且只有在检测到性能瓶颈时才使其变得更加复杂和难以理解。

大多数 Java 代码会创建大量短暂的对象,这些对象很快就会变得无法访问。出于这个原因,我相信 HotSpot 垃圾收集器是“分代的”,这意味着它将堆分成两个独立的区域:年轻代和老年代。

  • 年轻代——包含大部分新创建的对象,预计寿命很短。这个区域的垃圾收集效率很高

  • 老一代——寿命更长的对象最终会从年轻一代“提升”到老一代。垃圾收集这个区域更昂贵。

所以我不认为你担心创建太多短暂的对象是一个问题,因为垃圾收集器是专门为支持这个用例而设计的。

此外,HotSpot 更容易分析简单的代码,并可能进行内联/展开/优化,这还支持制作许多简单的对象和方法。

总而言之,继续让代码更容易理解和维护,这些代码会生成许多短命的对象。你不应该看到性能下降太多。分析它以查看实际性能。如果性能不符合您的喜好,您还可以重构瓶颈以使其更快。

于 2014-12-31T21:51:39.263 回答