Java 中的模运算速度慢吗?

Is modulo slow in Java?

我一直在查看 JDK 中 ThreadLocal 的实现,出于好奇,我发现了这个:

/**
 * Increment i modulo len.
 */
 private static int nextIndex(int i, int len) {
     return ((i + 1 < len) ? i + 1 : 0);
 }

这看起来很明显可以用一个简单的 return (i + 1) % len 来实现,但我认为这些人知道他们的东西。知道他们为什么这样做吗?

此代码高度注重性能,具有用于保存线程局部映射的自定义映射、帮助 GC 变得聪明的弱引用等等,所以我想这是一个性能问题。 Java 中的模运算速度慢吗?

% 出于性能原因在本例中被避免。

div/rem 操作即使在 CPU 架构级别上也较慢;不仅在 Java。例如,Haswell 上 idiv 指令的最小延迟约为 10 个周期,但 add.

仅 1 个周期

让我们使用 JMH 进行基准测试。

import org.openjdk.jmh.annotations.*;

@State(Scope.Benchmark)
public class Modulo {
    @Param("16")
    int len;

    int i;

    @Benchmark
    public int baseline() {
        return i;
    }

    @Benchmark
    public int conditional() {
        return i = (i + 1 < len) ? i + 1 : 0;
    }

    @Benchmark
    public int mask() {
        return i = (i + 1) & (len - 1);
    }

    @Benchmark
    public int mod() {
        return i = (i + 1) % len;
    }
}

结果:

Benchmark           (len)  Mode  Cnt  Score   Error  Units
Modulo.baseline        16  avgt   10  2,951 ± 0,038  ns/op
Modulo.conditional     16  avgt   10  3,517 ± 0,051  ns/op
Modulo.mask            16  avgt   10  3,765 ± 0,016  ns/op
Modulo.mod             16  avgt   10  9,125 ± 0,023  ns/op

如您所见,使用 % 比条件表达式慢 ~2.6 倍。 JIT 无法在讨论的 ThreadLocal 代码中自动优化它,因为除数 (table.length) 是可变的。

mod 而不是 Java。它分别作为整数和浮点数的字节码指令 iremfrem 实现。 JIT 在这方面做得很好。

在我的基准测试中(参见 article),irem 调用 JDK 1.8 大约需要 1 纳秒 。这相当快。 frem 调用速度大约慢 3 倍,因此请尽可能使用整数。

如果您使用自然整数(例如数组索引)和 2 除数的幂(例如 8 线程局部变量),那么您可以使用一些小技巧来获得 20% 的性能增益。