Может ли Java оптимизировать "мутирование" операций BigInteger в цикле?

Мне нужно иметь дело с большим количеством больших чисел, намного больших, чем длинный ( > 10 ^ 200), поэтому я использую BigIntegers. Наиболее распространенная операция, которую я выполняю, заключается в добавлении их к аккумулятору, например:

BigInteger A = new BigInteger("0");
for(BigInteger n : nums) {
    A = A.add(n);
}

Конечно, создание копий для деструктивных действий - довольно отходы (ну, пока имеется достаточно большой буфер), поэтому мне было интересно, может ли Java каким-то образом оптимизировать это (я слышал, что существует класс MutableBigInteger, не отображаемый математикой .java), или я должен просто написать свой собственный класс BigInteger.

Ответ 1

Да, существует класс java.math.MutableBigInteger, который используется BigInteger для интенсивных вычислений. К сожалению, он объявлен как закрытый, так что вы не можете его использовать. В библиотеке Apache Commons также есть класс MutableBigInteger, но это просто изменчивая оболочка для BigInteger, и это не поможет вам.

Мне было интересно, может ли Java каким-то образом оптимизировать это...

Нет... не выдерживая вышеуказанного.

или я должен просто написать свой собственный класс BigInteger.

Этот подход.

Другим является загрузка источников OpenJDK, поиск исходного кода для java.math.MutableBigInteger, изменение его имени пакета и доступа и его включение в вашу базу кода. Единственное препятствие заключается в том, что OpenJDK лицензируется под GPL (GPL-2, я думаю), и это имеет последствия, если вы когда-либо распространяли код с помощью модифицированного класса.

См. также:

Ответ 2

Более быстрое решение - обходить видимость пакета Java. Вы можете сделать это, создав пакет с именем java.math в своем собственном проекте и создав открытый класс, который предоставляет пакет private MutableBigInteger следующим образом:

package java.math;

public class PublicMutableBigInteger extends MutableBigInteger {

}

Затем вы можете просто импортировать java.math.PublicMutableBigInteger; и использовать его как любой другой класс. Это решение быстро и не налагает на вас никакой конкретной лицензии.

Ответ 3

Не так много компилятора, потому что он не может знать, что делает метод add. Вот сгенерированный код для тела цикла. Как вы можете видеть, он просто вызывает add и сохраняет результат.

   25:  iload   5
   27:  iload   4
   29:  if_icmpge       51
   32:  aload_3
   33:  iload   5
   35:  aaload
   36:  astore  6
   38:  aload_1
   39:  aload   6
   41:  invokevirtual   #5; //Method java/math/BigInteger.add:(Ljava/math/BigInteger;)Ljava/math/BigInteger;
   44:  astore_1
   45:  iinc    5, 1
   48:  goto    25

Теоретически, система времени работы виртуальной машины Java может быть более умной. Например, он может обнаружить, что один объект непрерывно перезаписывает только что выделенный, и просто заменяет два буфера распределения для них. Однако, как мы видим, запустив следующую программу с включенным протоколом сбора мусора, это, к сожалению, не так.

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Random;

class Test {
    public static void main(String[] args) {
    ArrayList <BigInteger> nums = new ArrayList<BigInteger>();
    final int NBITS = 100;
    final int NVALS = 1000000;

    System.out.println("Filling ArrayList");
    Random r = new Random();
    for (int i = 0; i < NVALS; i++)
        nums.add(new BigInteger(NBITS, r));

    System.out.println("Adding ArrayList values");
    BigInteger A = new BigInteger("0");
    for(BigInteger n : nums) {
        A = A.add(n);
    }

    System.gc();
    }
}

См. вызовы коллекции мусора во время процесса добавления.

C:\tmp>java -verbose:gc Test
Filling ArrayList
[GC 16256K->10471K(62336K), 0.0257655 secs]
[GC 26727K->21107K(78592K), 0.0304749 secs]
[GC 53619K->42090K(78592K), 0.0567912 secs]
[Full GC 42090K->42090K(122304K), 0.1019642 secs]
[GC 74602K->65857K(141760K), 0.0601406 secs]
[Full GC 65857K->65853K(182144K), 0.1485418 secs]
Adding ArrayList values
[GC 117821K->77213K(195200K), 0.0381312 secs]
[GC 112746K->77245K(228288K), 0.0111372 secs]
[Full GC 77245K->137K(228288K), 0.0327287 secs]

C:\tmp>java -version
java version "1.6.0_25"
Java(TM) SE Runtime Environment (build 1.6.0_25-b06)
Java HotSpot(TM) 64-Bit Server VM (build 20.0-b11, mixed mode)

Ответ 4

Java не будет делать особых оптимизаций для этого случая. BigInteger обычно рассматривается как обычный класс, как и любой другой (в отличие от String, например, который иногда получает некоторые специальные оптимизации при объединении многих строк).

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

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