Головоломка производительности Java: классы-оболочки быстрее, чем примитивные типы?

Чтобы реализовать некоторые алгоритмы анализа изображений, не беспокоясь о типе данных (т. е. не имея слишком много повторяющегося кода), я настраиваю шаблон посетителя для примитивных массивов в Java.

В приведенном ниже примере я определил два типа посетителей

  • примитивный тип, где подпись visit метод visit(int, int double)
  • универсальный тип, где подпись visit метод visit(int, int Double),

Из этого следует, что оба посетителя выполняют абсолютно одинаковые операции. Моя идея состояла в том, чтобы попытаться измерить стоимость бокса / распаковки.

Так вот полная программа

public class VisitorsBenchmark {
    public interface Array2DGenericVisitor<TYPE, RET> {

        void begin(int width, int height);

        RET end();

        void visit(int x, int y, TYPE value);
    }

    public interface Array2DPrimitiveVisitor<RET> {

        void begin(final int width, final int height);

        RET end();

        void visit(final int x, final int y, final double value);
    }

    public static <RET>
        RET
        accept(final int width,
               final int height,
               final double[] data,
               final Array2DGenericVisitor<Double, RET> visitor) {

        final int size = width * height;
        visitor.begin(width, height);
        for (int i = 0, x = 0, y = 0; i < size; i++) {
            visitor.visit(x, y, data[i]);
            x++;
            if (x == width) {
                x = 0;
                y++;
                if (y == height) {
                    y = 0;
                }
            }
        }
        return visitor.end();
    }

    public static <RET> RET accept(final int width,
                                   final int height,
                                   final double[] data,
                                   final Array2DPrimitiveVisitor<RET> visitor) {

        final int size = width * height;
        visitor.begin(width, height);
        for (int i = 0, x = 0, y = 0; i < size; i++) {
            visitor.visit(x, y, data[i]);
            x++;
            if (x == width) {
                x = 0;
                y++;
                if (y == height) {
                    y = 0;
                }
            }
        }
        return visitor.end();
    }

    private static final Array2DGenericVisitor<Double, double[]> generic;

    private static final Array2DPrimitiveVisitor<double[]> primitive;

    static {
        generic = new Array2DGenericVisitor<Double, double[]>() {
            private double[] sum;

            @Override
            public void begin(final int width, final int height) {

                final int length = (int) Math.ceil(Math.hypot(WIDTH, HEIGHT));
                sum = new double[length];
            }

            @Override
            public void visit(final int x, final int y, final Double value) {

                final int r = (int) Math.round(Math.sqrt(x * x + y * y));
                sum[r] += value;
            }

            @Override
            public double[] end() {

                return sum;
            }
        };

        primitive = new Array2DPrimitiveVisitor<double[]>() {
            private double[] sum;

            @Override
            public void begin(final int width, final int height) {

                final int length = (int) Math.ceil(Math.hypot(WIDTH, HEIGHT));
                sum = new double[length];
            }

            @Override
            public void visit(final int x, final int y, final double value) {

                final int r = (int) Math.round(Math.sqrt(x * x + y * y));
                sum[r] += value;
            }

            @Override
            public double[] end() {

                return sum;
            }
        };
    }

    private static final int WIDTH = 300;

    private static final int HEIGHT = 300;

    private static final int NUM_ITERATIONS_PREHEATING = 10000;

    private static final int NUM_ITERATIONS_BENCHMARKING = 10000;

    public static void main(String[] args) {

        final double[] data = new double[WIDTH * HEIGHT];
        for (int i = 0; i < data.length; i++) {
            data[i] = Math.random();
        }

        /*
         * Pre-heating.
         */
        for (int i = 0; i < NUM_ITERATIONS_PREHEATING; i++) {
            accept(WIDTH, HEIGHT, data, generic);
        }
        for (int i = 0; i < NUM_ITERATIONS_PREHEATING; i++) {
            accept(WIDTH, HEIGHT, data, primitive);
        }

        /*
         * Benchmarking proper.
         */
        double[] sumPrimitive = null;
        double[] sumGeneric = null;

        double aux = System.nanoTime();
        for (int i = 0; i < NUM_ITERATIONS_BENCHMARKING; i++) {
            sumGeneric = accept(WIDTH, HEIGHT, data, generic);
        }
        final double timeGeneric = System.nanoTime() - aux;

        aux = System.nanoTime();
        for (int i = 0; i < NUM_ITERATIONS_BENCHMARKING; i++) {
            sumPrimitive = accept(WIDTH, HEIGHT, data, primitive);
        }
        final double timePrimitive = System.nanoTime() - aux;

        System.out.println("prim = " + timePrimitive);
        System.out.println("generic = " + timeGeneric);
        System.out.println("generic / primitive = "
                           + (timeGeneric / timePrimitive));
    }
}

Я знаю, что JIT довольно умный, поэтому я не слишком удивился, когда оба посетителя показали одинаково хорошие результаты. Что еще более удивительно, так это то, что обычный посетитель работает немного быстрее, чем примитив, что является неожиданным. Я знаю, что бенчмаркинг иногда может быть трудным, поэтому я, должно быть, сделал что-то не так. Можете ли вы определить ошибку?

Большое спасибо за вашу помощь!!! Себастьен

[EDIT] Я обновил код для учета фазы предварительного нагрева (чтобы позволить компилятору JIT выполнять свою работу). Это не меняет результаты, которые постоянно ниже 1 (0,95 - 0,98).

3 ответа

Я знаю, что бенчмаркинг иногда может быть трудным, поэтому я, должно быть, сделал что-то не так. Можете ли вы определить ошибку?

Я думаю, что проблема в том, что ваш тест не учитывает прогрев JVM. Поместите взять тело вашего основного метода и поместите его в другой метод. Тогда ваш main метод вызывает этот новый метод несколько раз в цикле. Наконец, изучите результаты и отбросьте первые несколько, которые искажены компиляцией JIT и другими эффектами разминки.

Маленькие советы:

  • Не использовать Math.random() выполнить тесты, так как результаты не являются детерминированными. Вам нужно что-то вроде new Random(xxx),
  • Всегда печатайте результат операции. Смешивание типов эталонных тестов в одном исполнении - плохая практика, так как это может привести к различной оптимизации сайта вызовов (но не в вашем случае)
  • double aux = System.nanoTime (); -- не все longs вписаться в двойники - правильно.
  • опубликовать спецификацию среды и оборудования, на котором вы выполняете тесты
  • распечатывать тестирование при включении печати компиляции -XX:-PrintCompilation и сборка мусора -verbosegc -XX:+PrintGCDetails - GC может включиться во время "неправильного" теста настолько, чтобы исказить результаты.


Редактировать:

Я проверил сгенерированный ассемблер, и ни одна из них не является настоящей причиной. Double.valueOf() не выделяется, так как метод полностью встроен и оптимизирован - он использует только регистры процессора. Однако без аппаратной спецификации /JVM нет реального ответа.

Я нашел JVM (1.6.0.26), где общая версия (Double) лучше развернуть цикл (!) благодаря более глубокому анализу (очевидно, Double.valueOf()) и возможно постоянное сворачивание WIDTH/HEIGHT. Измените ШИРИНУ / ВЫСОТУ на некоторые простые числа, и результаты должны отличаться.


Суть в том, что не используйте микробенчмарки, если вы не знаете, как оптимизирует JVM, и не проверяйте сгенерированный машинный код.


Отказ от ответственности: я не инженер JVM

Это абсолютно "предположение с диким ослом", но я думаю, что это связано с копированием байтов в стек. Передача простого примитива включает копирование 8 байт в стек. Передача Double требует только копирования указателя.

Другие вопросы по тегам