Может ли 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 действительно является вашим узким местом, тогда может иметь смысл использовать настраиваемый изменяемый класс с большим целым числом, чтобы действовать как накопитель. Но я бы не сделал этого, прежде чем вы доказали, что это поистине главное узкое место.