web-dev-qa-db-ja.com

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でモジュロが遅いですか?

21
Dici

この例では、パフォーマンス上の理由から%は避けています。

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倍遅くなります。除数(table.length)は可変であるため、JITは説明したThreadLocalコードでこれを自動的に最適化できません。

27
apangin

modnotJavaでは遅くなります。これは、整数と浮動小数点数に対してそれぞれバイトコード命令iremfremとして実装されます。 JITはこれを最適化するのに良い仕事をします。

私のベンチマーク( 記事 を参照)では、JDK1.8でのirem呼び出しには約1ナノ秒かかります。それはかなり速いです。 frem呼び出しは約3倍遅いので、可能な場合は整数を使用してください。

Natural Integers(例:配列インデックス)と2の累乗(例:8スレッドローカル)を使用している場合は、少し調整するトリックを使用して20%のパフォーマンス向上を得ることができます。

2
Joseph Lust