Java中Record比常规类快约 10%


来自Oracle 的Java核心库开发人员: Hotspot VM 以特殊方式信任 Java 记录,与常规 Java 类相比,这使得它们的速度在某些方面更加优越。


假设我们想要建模一个不可变点:

public interface Point {
    int x();
    int y();
}

在 Java 中引入记录类之前  ,必须使用常规 Java 类“手动”编码数据类,如下所示:

public final class RegularPoint implements Point {

    private final int x;
    private final int y;

    public RegularPoint(int x, int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public int x() {
        return x;
    }

    @Override
    public int y() {
        return y;
    }

    // Implementations of toString(), hashCode() and equals()
   
// omitted for brevity

}

有了记录,一切变得更加容易,而且我们还获得了方法 toString()、 hashCode() 和 的合理默认实现equals():

public record RecordPoint(int x, int y) implements Point {}


基准测试:

这是一个基准,可用于衡量使用记录相对于常规类的效果:

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 5, time = 1)
@Measurement(iterations = 5, time = 1)
@Fork(value=3)
public class Bench {

    private static final RegularPoint REGULAR_ORIGIN = new RegularPoint(0, 0);
    private static final RecordPoint RECORD_ORIGIN = new RecordPoint(0, 0);

    private List<RegularPoint> regularPoints;
    private List<RecordPoint> recordPoints;

    @Setup
    public void setup() {
        regularPoints = IntStream.range(0, 16)
                .mapToObj(i -> new RegularPoint(i, i))
                .toList();

        recordPoints = IntStream.range(0, 16)
                .mapToObj(i -> new RecordPoint(i, i))
                .toList();
    }

    @Benchmark
    public void regular(Blackhole bh) {
        for (RegularPoint point: regularPoints) {
            if (point.x() == REGULAR_ORIGIN.x() && point.y() == REGULAR_ORIGIN.y()) {
                bh.consume(1);
            } else {
                bh.consume(0);
            }
        }
    }

    @Benchmark
    public void record(Blackhole bh) {
        for (RecordPoint point: recordPoints) {
            if (point.x() == RECORD_ORIGIN.x() && point.y() == RECORD_ORIGIN.y()) {
                bh.consume(1);
            } else {
                bh.consume(0);
            }
        }
    }

    public static void main(String[] args) throws Exception {
        org.openjdk.jmh.Main.main(args);
    }

}

在 Mac M1 笔记本电脑上运行时,出现以下结果(越低越好):

Benchmark      Mode  Cnt   Score   Error  Units
Bench.regular  avgt   15  10.424 ± 0.257  ns/op
Bench.record   avgt   15   9.412 ± 0.181  ns/op

可以看出,在此基准测试中,记录比常规类快约 10%。

为什么记录在上述情况下会更快?
类 ciField.cpp中有一条线索 ,它告诉 Hotspot 编译器在执行常量折叠时哪些实例字段应该“可信”。
一个例子是 外部函数和内存 API, 该 API 预计将在 Java 22 中最终确定,例如,实现各种 MemoryLayout 变体的所有类都有资格进行常量折叠优化。

上面的 C++ 类不适用于常规 Java 程序,但是通过切换到记录,我们可以直接获得实例字段常量折叠的好处。

记录提供了一种表达数据载体的便捷方式。作为一个额外的好处,与某些应用程序中的常规 Java 类相比,它们还提供了改进的性能。