Повышение производительности BigInteger Java
Как увеличить производительность большого целого числа Java?
например, эта программа вычисления факториала:
import java.math.*;
class Fac {
public static void main(String[] args) {
BigInteger i = BigInteger.ONE;
for(BigInteger z=BigInteger.valueOf(2);z.compareTo(BigInteger.valueOf(99999)) != 0;) {
i = i.multiply(z);
z = z.add(BigInteger.ONE);
}
System.out.println( i );
}
}
эта программа завершена в 31.5
s
где в C++:
#include <iostream>
#include <gmpxx.h>
using namespace std;
int main() {
mpz_class r;
r = 1;
for(int z=2;z<99999;++z) {
r *= mpz_class(z);
}
cout << r << endl;
}
завершено в 1.0
s
и Ruby (для сравнения):
puts (2...99999).inject(:*)
завершено в 4.4
s (Рубин) и 32.2
s в JRuby
а также Go (для сравнения):
package main
import (
"fmt"
"math/big"
)
func main() {
i := big.NewInt(1);
one := big.NewInt(1)
for z := big.NewInt(2); z.Cmp(big.NewInt(99999)) < 0; {
i.Mul(i,z);
z.Add(z,one)
}
fmt.Println( i );
}
завершено в 1.6
s и 0.7
s для MulRange
редактировать как и просили:
import java.math.*;
class F2 {
public static void main(String[] args) {
BigInteger i = BigInteger.ONE, r = BigInteger.valueOf(2);
for(int z=2; z<99999 ; ++z) {
i = i.multiply(r);
r = r.add(BigInteger.ONE);
}
System.out.println( i );
}
}
срок выполнения: 31.4
s
правка 2 для тех, кто все еще думает, что первый и второй Java-код несправедлив..
import java.math.*;
class F3 {
public static void main(String[] args) {
BigInteger i = BigInteger.ONE;
for(int z=2; z<99999 ; ++z) {
i = i.multiply(BigInteger.valueOf(z));
}
System.out.println( i );
}
}
завершено в 31.1
s
правка 3 @OldCurmudgeon комментарий:
import java.math.*;
import java.lang.reflect.*;
class F4 {
public static void main(String[] args) {
try {
Constructor<?> Bignum = Class.forName("java.math.MutableBigInteger").getDeclaredConstructor(int.class);
Bignum.setAccessible(true);
Object i = Bignum.newInstance(1);
Method m = i.getClass().getDeclaredMethod("mul", new Class[] { int.class, i.getClass()});
m.setAccessible(true);
for(int z=2; z<99999 ; ++z) {
m.invoke(i, z, i);
}
System.out.println( i );
} catch(Exception e) { System.err.println(e); }
}
}
завершено в 23.7
s
изменить 4 как заявленная @Marco13 самая большая проблема заключалась в создании строки, а не в самом BigInteger..
- BigInteger:
3.0
s - MutableBigInteger hack:
10.1
s - создание строки: ~
20
s
4 ответов
само вычисление не должно занять так много времени. The создание строки может занять некоторое время.
эта программа (Слава OldCurmudgeon и https://stackoverflow.com/a/8583188/823393) занимает примерно 3,9 секунды на ядре I7, 3GHz, Java 7/21, при запуске с -Xmx1000m -sever
:
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class FastBigInteger
{
public static void main(String[] args)
{
try
{
Class<?> c = Class.forName("java.math.MutableBigInteger");
Constructor<?> con = c.getDeclaredConstructor(int.class);
con.setAccessible(true);
Object i = con.newInstance(1);
Method m = c.getDeclaredMethod("mul", new Class[] { int.class, c });
m.setAccessible(true);
long before = System.nanoTime();
for (int z = 2; z < 99999; ++z)
{
m.invoke(i, z, i);
}
long after = System.nanoTime();
System.out.println("Duration "+(after-before)/1e9);
String s = i.toString();
int n = s.length();
int lineWidth = 200;
for (int j=0; j<n; j+=lineWidth)
{
int j0 = j;
int j1 = Math.min(s.length(), j+lineWidth);
System.out.println(s.substring(j0, j1));
}
}
catch (Exception e)
{
System.err.println(e);
}
}
}
после печати продолжительности для фактического вычисления требуется довольно много времени, пока он не закончит создание строки, но это должно вряд ли здесь стоит принимать во внимание.
Это все равно не разумный ориентир, но показывает, что по крайней мере нет проблем с самим вычислением.
но, по общему признанию, при использовании только BigInteger
вместо MutableBigInteger
hack, это занимает appx. 15 секунд, что довольно плохо по сравнению с реализацией на C++.
начать с:
import java.math.*;
class Fac {
public static void main(String[] args) {
BigInteger i = BigInteger.ONE;
BigInteger maxValue = BigInteger.valueOf(99999);
for(BigInteger z=BigInteger.valueOf(2); z.compareTo(maxValue) != 0;) {
i = i.multiply(z);
z = z.add(BigInteger.ONE);
}
System.out.println( i );
}
}
.valueOf source
1081 public static BigInteger More ...valueOf(long val) {
1082 // If -MAX_CONSTANT < val < MAX_CONSTANT, return stashed constant
1083 if (val == 0)
1084 return ZERO;
1085 if (val > 0 && val <= MAX_CONSTANT)
1086 return posConst[(int) val];
1087 else if (val < 0 && val >= -MAX_CONSTANT)
1088 return negConst[(int) -val];
1089
1090 return new BigInteger(val);
1091 }
Он будет создавать новый BigInteger каждый раз, так как MAX_CONSTANT
- это 16.
Я думаю, что это может идти медленнее, потому что GC начинает собирать некоторые старые BigInteger
экземпляры, но в любом случае вы всегда должны использовать int и long.. здесь BigInteger на самом деле не нужен.
после вашего последнего теста я думаю, что мы можем быть уверены, что это может быть вызвано GC.
У меня есть код clojure, вычисляющий 100 000-е число Фибоначчи, используя большие целые числа. Теперь этот поток не о clojure, но поскольку clojure работает на JVM, и я запустил бенчмарки на некоторых существующих больших целочисленных реализациях, я почувствовал, что комментарий здесь может быть ценным.
алгоритм при использовании класса JVM BigInteger (обозначается синтаксисом xn-литерала в clojure) выглядит следующим образом:
(defn fibo [n]
(loop [i n a 1N b 1N]
(if (> i 0)
(recur (dec i) b (+ a b))
a)))
Я повторно реализовал это, используя четыре больших целого числа реализаций и я побежал тестов в Clojure Критериум библиотека, которая делает разминки и некоторый статистический анализ, чтобы попытаться добраться до нескольких соответствующих чисел.
результаты на моем 2,8 ГГц Intel Core i7 macbook:
- apfloat apint класс - 964 МС
- в JVM BigInteger класс - 130 МС
- jscience LargeInteger класс - 104 МС
- Хулдра тип bigint класс - 60 мс
теперь я понимаю, что это все анекдотические и что мы только измеряем добавление здесь, но я должен сказать, что фраза huldra catch "превосходящая BigInteger с 2015 года" кажется довольно точной в этом случае.
любые комментарии с указателями на потенциальных кандидатов для более быстрых алгоритмов сложения big int очень ценятся.
другие ответы связаны с настройкой производительности с помощью кода.
Если вы используете версию java менее 1.8.051, вы можете настроить большую целочисленную производительность, используя следующие параметры команды:
-XX:-UseMontgomerySquareIntrinsic
-XX:-UseMontgomeryMultiplyIntrinsic
-XX:-UseSquareToLenIntrinsic
-XX:-UseMultiplyToLenIntrinsic
после 1.8.051 эти параметры включены по умолчанию.