Целочисленная производительность - разница в 30-50 раз x32 против x64 jvm? - программирование
Подтвердить что ты не робот

Целочисленная производительность - разница в 30-50 раз x32 против x64 jvm?

В последнее время у меня очень странная вещь: один метод был чрезвычайно медленным при профайлере без очевидной причины. Он содержит несколько операций с long, но вызывается довольно часто - его общее использование составляло около 30-40% от общего времени программы, тогда как другие части выглядят намного "тяжелее".

Обычно я запускаю программы, не содержащие памяти, на JVM x32, но, предполагая, что у меня проблема с 64-разрядным типом, я попытался запустить то же самое на x64 JVM - общая производительность в "реальном сценарии" была на 2-3 раза лучше. После этого я создал тесты JMH для операций из определенного метода и был шокирован разницей в JVM x32 и x64 - до 50 раз.

Я бы "согласился" примерно в 2 раза медленнее x32 JVM (размер меньшего слова), но у меня нет никаких подсказок, откуда может появиться 30-50 раз. Можете ли вы объяснить это резкое различие?


Ответы на комментарии:

  • Я переписал тестовый код, чтобы "вернуть что-то" и избежать "устранения мертвого кода". Похоже, что он ничего не изменил для "x32", но некоторые методы на "x64" были значительно медленнее.
  • Оба теста выполнялись под "клиентом". Работа под "-server" не имела заметного эффекта.

Так что кажется, что ответ на мой вопрос

  • "тестовый код" был неправильным: из-за "отсутствия возвращаемого значения" он разрешил JVM делать "удаление мертвого кода" или любую другую оптимизацию, и похоже, что "x32 JVM" делает меньше таких оптимизаций, чем "x64 JVM", - которые вызвали такие значительная "ложная" разница между x32 и x64
  • Первичная разница по "правильному тестовому коду" составляет до 2x-5 раз - это кажется разумным

Вот результаты (Примечание ? 10?? - специальные символы, не напечатанные в Windows - это что-то ниже 0,001 с/у, написанное в научной нотации как 10e-??)

x32 1.8.0_152

Benchmark                Mode  Score Units    Score (with 'return')
IntVsLong.cycleInt       avgt  0.035  s/op    0.034   (?x slower vs. x64)
IntVsLong.cycleLong      avgt  0.106  s/op    0.099   (3x slower vs. x64) 
IntVsLong.divDoubleInt   avgt  0.462  s/op    0.459
IntVsLong.divDoubleLong  avgt  1.658  s/op    1.724   (2x slower vs. x64)
IntVsLong.divInt         avgt  0.335  s/op    0.373
IntVsLong.divLong        avgt  1.380  s/op    1.399
IntVsLong.l2i            avgt  0.101  s/op    0.197   (3x slower vs. x64)  
IntVsLong.mulInt         avgt  0.067  s/op    0.068
IntVsLong.mulLong        avgt  0.278  s/op    0.337   (5x slower vs. x64)
IntVsLong.subInt         avgt  0.067  s/op    0.067   (?x slower vs. x64)
IntVsLong.subLong        avgt  0.243  s/op    0.300   (4x slower vs. x64)

x64 1.8.0_152

Benchmark                Mode  Score Units    Score (with 'return')
IntVsLong.cycleInt       avgt ? 10??  s/op   ? 10??
IntVsLong.cycleLong      avgt  0.035  s/op    0.034
IntVsLong.divDoubleInt   avgt  0.045  s/op    0.788 (was dead)
IntVsLong.divDoubleLong  avgt  0.033  s/op    0.787 (was dead)
IntVsLong.divInt         avgt ? 10??  s/op    0.302 (was dead)
IntVsLong.divLong        avgt  0.046  s/op    1.098 (was dead)
IntVsLong.l2i            avgt  0.037  s/op    0.067
IntVsLong.mulInt         avgt ? 10??  s/op    0.052 (was dead)
IntVsLong.mulLong        avgt  0.040  s/op    0.067
IntVsLong.subInt         avgt ? 10??  s/op   ? 10??
IntVsLong.subLong        avgt  0.075  s/op    0.082

И вот код (фиксированный)

import org.openjdk.jmh.annotations.Benchmark;

public class IntVsLong {

    public static int N_REPEAT_I  = 100_000_000;
    public static long N_REPEAT_L = 100_000_000;

    public static int CONST_I = 3;
    public static long CONST_L = 3;
    public static double CONST_D = 3;

    @Benchmark
    public void cycleInt() throws InterruptedException {
        for( int i = 0; i < N_REPEAT_I; i++ ) {
        }
    }

    @Benchmark
    public void cycleLong() throws InterruptedException {
        for( long i = 0; i < N_REPEAT_L; i++ ) {
        }
    }

    @Benchmark
    public int divInt() throws InterruptedException {
        int r = 0;
        for( int i = 0; i < N_REPEAT_I; i++ ) {
            r += i / CONST_I;
        }
        return r;
    }

    @Benchmark
    public long divLong() throws InterruptedException {
        long r = 0;
        for( long i = 0; i < N_REPEAT_L; i++ ) {
            r += i / CONST_L;
        }
        return r;
    }

    @Benchmark
    public double divDoubleInt() throws InterruptedException {
        double r = 0;
        for( int i = 1; i < N_REPEAT_L; i++ ) {
            r += CONST_D / i;
        }
        return r;
    }

    @Benchmark
    public double divDoubleLong() throws InterruptedException {
        double r = 0;
        for( long i = 1; i < N_REPEAT_L; i++ ) {
            r += CONST_D / i;
        }
        return r;
    }

    @Benchmark
    public int mulInt() throws InterruptedException {
        int r = 0;
        for( int i = 0; i < N_REPEAT_I; i++ ) {
            r += i * CONST_I;
        }
        return r;
    }

    @Benchmark
    public long mulLong() throws InterruptedException {
        long r = 0;
        for( long i = 0; i < N_REPEAT_L; i++ ) {
            r += i * CONST_L;
        }
        return r;
    }

    @Benchmark
    public int subInt() throws InterruptedException {
        int r = 0;
        for( int i = 0; i < N_REPEAT_I; i++ ) {
            r += i - r;
        }
        return r;
    }

    @Benchmark
    public long subLong() throws InterruptedException {
        long r = 0;
        for( long i = 0; i < N_REPEAT_L; i++ ) {
            r += i - r;
        }
        return r;
    }

    @Benchmark
    public long l2i() throws InterruptedException {
        int r = 0;
        for( long i = 0; i < N_REPEAT_L; i++ ) {
            r += (int)i;
        }
        return r;
    }

}
4b9b3361

Ответ 1

Там много переменных для проверки.

Если мы посмотрим только на процессор с 64-разрядным, вы можете адресовать больше операций в регистры процессора на том же шаге, что и октеты eitht вместо четырех осетов для каждого реестра. Это увеличивает производительность операций и распределение памяти. Также некоторые процессоры поддерживают только расширенные функции, работающие только в режиме 64 бит

Если вы используете один и тот же CPU для выполнения тестов, вы должны принять во внимание, что для выполнения 32-битных инструкций процессор должен работать в виртуальном режиме или в защищенном режиме, который работает медленно, чем настоящий 32-битный процессор. Также некоторые из расширений набора инструкций, вероятно, не могли быть задействованы с использованием 32-битного режима, такого как SSE-SIMD или AVX taht, который мог бы увеличить некоторую скорость работы.

Также, если вы используете современную ОС, такую как Windows 10, вы должны учитывать, что ОС запускает 32-битные приложения с использованием WOW64 (эмулятор x86)

Помогите документу: