Java неизменный класс намного медленнее

Мне понадобилась какая-то сложная математическая библиотека, поэтому я колебался между библиотеками, которые используют неизменяемый Complex и библиотеки, которые используют mutable Complex. Очевидно, я хочу, чтобы вычисления выполнялись достаточно быстро (если только он не убивает читаемость и т.д.).

Итак, я создал простой тест скорости mutable vs immutable:

final class MutableInt {
    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public MutableInt() {
        this(0);
    }

    public MutableInt(int value) {
        this.value = value;
    }   
}

final class ImmutableInt {
    private final int value;

    public ImmutableInt(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}

public class TestImmutableSpeed {

    static long testMutable(final int arrLen) {
        MutableInt[] arrMutable = new MutableInt[arrLen];
        for (int i = 0; i < arrMutable.length; ++i) {
            arrMutable[i] = new MutableInt(i);
            for (int j = 0; j < arrMutable.length; ++j) {
                arrMutable[i].setValue(arrMutable[i].getValue() + j);
            }
        }
        long sumMutable = 0;
        for (MutableInt item : arrMutable) {
            sumMutable += item.getValue();
        }
        return sumMutable;
    }

    static long testImmutable(final int arrLen) {
        ImmutableInt[] arrImmutable = new ImmutableInt[arrLen];
        for (int i = 0; i < arrImmutable.length; ++i) {
            arrImmutable[i] = new ImmutableInt(i);
            for (int j = 0; j < arrImmutable.length; ++j) {
                arrImmutable[i] = new ImmutableInt(arrImmutable[i].getValue() + j);
            }
        }
        long sumImmutable = 0;
        for (ImmutableInt item : arrImmutable) {
            sumImmutable += item.getValue();
        }
        return sumImmutable;
    }

    public static void main(String[] args) {
        final int arrLen = 1<<14;

        long tmStart = System.nanoTime();
        System.out.println("sum = " + testMutable(arrLen));
        long tmMid = System.nanoTime();
        System.out.println("sum = " + testImmutable(arrLen));
        long tmEnd = System.nanoTime();

        System.out.println("speed comparison mutable vs immutable:");
        System.out.println("mutable   " + (tmMid - tmStart)/1000000 + " ms");
        System.out.println("immutable " + (tmEnd - tmMid)/1000000 + " ms");
    }
}

Вы можете настроить размер массива, если тест выполняется слишком медленно/быстро.

Я запускаю с: -server -Xms256m -XX: + AggressiveOpts И я получаю:

sum = 2199023247360
sum = 2199023247360
speed comparison mutable vs immutable:
mutable   102 ms
immutable 1506 ms

Вопрос: Не хватает ли параметра оптимизации, или является неизменной версией 15x медленнее?

Если это так, зачем кому-нибудь писать математическую библиотеку с неизменяемым классом Complex в ней? Неизменяется просто "причудливо", но бесполезно?

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

Изменить: Я снова запустил этот микрообъект с помощью суппорта, как это было предложено одним ответом, и он работает на 12x медленнее, а не на 15 раз, по-прежнему остается той же точкой. Изменен код для тестирования Caliper:

import com.google.caliper.Runner;
import com.google.caliper.SimpleBenchmark;



final class MutableInt {
    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }

    public MutableInt() {
        this(0);
    }

    public MutableInt(int value) {
        this.value = value;
    }   
}

final class ImmutableInt {
    private final int value;

    public ImmutableInt(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }
}


public class TestImmutableSpeed extends SimpleBenchmark {

    static long testMutable(final int arrLen) {
        MutableInt[] arrMutable = new MutableInt[arrLen];
        for (int i = 0; i 

Выход суппорта:

 0% Scenario{vm=java, trial=0, benchmark=Mutable, type=-server, minMemory=-Xms256m, optimizations=-XX:+AggressiveOpts} 91614044.60 ns; ?=250338.20 ns @ 3 trials
50% Scenario{vm=java, trial=0, benchmark=Immutable, type=-server, minMemory=-Xms256m, optimizations=-XX:+AggressiveOpts} 1108057922.00 ns; ?=3920760.98 ns @ 3 trials

benchmark     ms linear runtime
  Mutable   91.6 ==
Immutable 1108.1 ==============================

Обратите внимание, что без параметров оптимизации для выхода JVM для суппорта:

 0% Scenario{vm=java, trial=0, benchmark=Mutable} 516562214.00 ns; ?=623120.57 ns @ 3 trials
50% Scenario{vm=java, trial=0, benchmark=Immutable} 1706758503.00 ns; ?=5842389.60 ns @ 3 trials

benchmark   ms linear runtime
  Mutable  517 =========
Immutable 1707 ==============================

Такие плохие параметры делают обе версии медленными, но соотношение менее страшное (но не важно, все же).

Ответ 1

Неизбежность приходит иногда с ограничением скорости. Используйте математическую библиотеку с изменяемым комплексом, если скорость имеет значение.

Ответ 2

Это захватывающе. Ну, во-первых, это не честный тест; вы не разогреваете JVM, когда делаете это именно так. Бенчмаркинг обычно очень трудно сделать. Я реорганизовал ваш код, чтобы использовать Google Caliper и получил похожие, но разные результаты; неизменный класс был только в 3 раза медленнее. Пока не знаю, почему. В любом случае, работа до сих пор:

TestImmutableSpeed.java

import com.google.caliper.Runner;
import com.google.caliper.SimpleBenchmark;

public class TestImmutableSpeed {
    static final class MutableInt {
        private int value;

        public int getValue() {
            return value;
        }

        public void setValue(int value) {
            this.value = value;
        }

        public MutableInt() {
            this(0);
        }

        public MutableInt(int value) {
            this.value = value;
        }   
    }

    static final class ImmutableInt {
        private final int value;

        public ImmutableInt(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }

    public static class TestBenchmark extends SimpleBenchmark {
        public void timeMutable(final int arrLen) {
            MutableInt[] arrMutable = new MutableInt[arrLen];
            for (int i = 0; i < arrMutable.length; ++i) {
                arrMutable[i] = new MutableInt(i);
                for (int j = 0; j < arrMutable.length; ++j) {
                    arrMutable[i].setValue(arrMutable[i].getValue() + j);
                }
            }
            long sumMutable = 0;
            for (MutableInt item : arrMutable) {
                sumMutable += item.getValue();
            }
            System.out.println(sumMutable);
        }

        public void timeImmutable(final int arrLen) {
            ImmutableInt[] arrImmutable = new ImmutableInt[arrLen];
            for (int i = 0; i < arrImmutable.length; ++i) {
                arrImmutable[i] = new ImmutableInt(i);
                for (int j = 0; j < arrImmutable.length; ++j) {
                    arrImmutable[i] = new ImmutableInt(arrImmutable[i].getValue() + j);
                }
            }
            long sumImmutable = 0;
            for (ImmutableInt item : arrImmutable) {
                sumImmutable += item.getValue();
            }
            System.out.println(sumImmutable);
        }
    }

    public static void main(String[] args) {
        Runner.main(TestBenchmark.class, new String[0]);
    }
}

Выход суппорта

 0% Scenario{vm=java, trial=0, benchmark=Immutable} 78574.05 ns; σ=21336.61 ns @ 10 trials
 50% Scenario{vm=java, trial=0, benchmark=Mutable} 24956.94 ns; σ=7267.78 ns @ 10 trials

 benchmark   us linear runtime
 Immutable 78.6 ==============================
   Mutable 25.0 =========

 vm: java
 trial: 0

Обновление строки

Итак, я думал об этом больше, и я решил попробовать изменить обернутый класс из int на объект, в данном случае a String. Меняя статические классы на String s и загружая строки с помощью Integer.valueOf(i).toString(), а вместо добавления, добавляя их в StringBuilder, я получил следующие результаты:

 0% Scenario{vm=java, trial=0, benchmark=Immutable} 11034616.91 ns; σ=7006742.43 ns @ 10 trials
50% Scenario{vm=java, trial=0, benchmark=Mutable} 9494963.68 ns; σ=6201410.87 ns @ 10 trials

benchmark    ms linear runtime
Immutable 11.03 ==============================
  Mutable  9.49 =========================

vm: java
trial: 0

Однако, я думаю, в этом случае разница преобладает во всех копиях массива, которые должны были произойти, а не в том, что он использовал String s.

Ответ 3

Неизменяемые значения делают чистое программирование на Java-чище. Вам не нужно копировать повсюду, чтобы избежать попадания в жуткий действие на расстоянии (под которым я подразумеваю, что изменение значения в одном месте непреднамеренно изменяет значение в другом). Удаление копии ускоряет работу в местах, но создание новых экземпляров замедляет работу в других областях.

(С++ интересен тем, что использует противоположный подход: вы получаете копии в четко определенных точках без необходимости писать код. Действительно, вам нужно написать код, чтобы удалить копирование.)

Если ваша проблема связана с производительностью, изменчивый комплекс тоже не подходит. Гораздо лучше иметь, скажем, сложный класс массива, который использует единственный двойной массив, скрытый в реализации, или просто двойной массив raw.

Еще в девяностые годы Гай Стил упомянул идею добавления типов значений в Java в качестве части завершения самого языка. Хотя это было очень ограниченное предложение, аналогичное structs С#, представленный позже, но не справляется с возможно самым очевидным классом значений в Java, строка.