Подтвердить что ты не робот

Является ли модуляция медленной в Java?

Я смотрел на реализацию ThreadLocal в JDK, из любопытства, и я нашел это:

/**
 * 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?

4b9b3361

Ответ 1

% можно избежать по причинам производительности в этом примере.

Операции

div/rem медленнее даже на уровне архитектуры ЦП; не только на Java. Например, минимальная латентность инструкции idiv на Haswell составляет около 10 циклов, но только 1 цикл для add.

Давайте проверим с помощью 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.6x медленнее условного выражения. JIT не может оптимизировать это автоматически в обсуждаемом коде ThreadLocal, потому что делитель (table.length) является переменным.

Ответ 2

mod не, что замедляется на Java. Он реализован в виде команд байтового кода irem и frem для целых чисел и поплавков соответственно. JIT делает хорошую работу по оптимизации этого.

В моих тестах (см. статья), irem вызовы в JDK 1.8 берут около 1 наносекунды. Это довольно быстро. frem вызовы примерно на 3 раза медленнее, поэтому, если возможно, используйте целые числа.

Если вы используете Natural Integer (например, индексирование массива) и мощность 2 Divisor (например, 8 локаторов потоков), вы можете использовать трюк с небольшим трюком, чтобы получить 20% прироста производительности.