最近看 HashMap 的源码。有一个 tableSizeFor 方法,原来在 JDK7 的时候,这个方法叫做 roundUpToPowerOf2:
static int roundUpToPowerOf2(int cap) { return cap >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (cap > 1) ? Integer.highestOneBit((cap - 1) << 1) : 1; } 在 JDK8 的时候给改成了这样:
static int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1 } 但是在 JDK11 的时候,对这块又进行了一个优化,可以参考 JDK-8203279, 给改成了下面的代码:
static int tableSizeFor(int cap) { int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1); return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; } 这个方法的用途其实没有变: 都是计算返回大于等于 cap 且与之最接近的一个 2 的次幂值。 为了验证这个方法的性能,我用 JHM 跑了一下基准测试,测试代码:
@BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iteratiOns= 2, time = 1, timeUnit = TimeUnit.SECONDS) // 预热 2 轮,每次 1s @Measurement(iteratiOns= 5, time = 1, timeUnit = TimeUnit.SECONDS) // 测试 5 轮,每次 1s @Fork(1) @State(Scope.Thread) public class TableSizeForTest { static final int MAXIMUM_CAPACITY = 1 << 30; static final int roundSize = 100_000_000; public static void main(String[] args) throws RunnerException { Options opt = new OptionsBuilder() .include(TableSizeForTest.class.getSimpleName()) .build(); new Runner(opt).run(); } @Benchmark public void jdk8() { for (int i = 1; i <= roundSize; i++) { tableSizeFor(i); } } @Benchmark public void jdk7() { for (int i = 1; i <= roundSize; i++) { roundUpToPowerOf2(i); } } @Benchmark public void jdk11() { for (int i = 1; i <= roundSize; i++) { tableSizeFor_JDK11(i); } } // 字数原因不贴 tableSizeFor 方法了 } 最后结果如下:
Benchmark Mode Cnt Score Error Units TableSizeForTest.jdk11 avgt 5 88015092.847 ± 57126449.190 ns/op TableSizeForTest.jdk7 avgt 5 0.397 ± 0.042 ns/op TableSizeForTest.jdk8 avgt 5 123458345.756 ± 2528234.087 ns/op 可以看到,jdk11 确实比 jdk8 的性能提高了不少,但是 相比于 jdk7 时使用的 roundUpToPowerOf2 方法,差距也太大了,有大佬能给说一下这是我测试的问题,还是说确实就是这样?
