Повышение производительности 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.5s

где в 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.0s

и Ruby (для сравнения):

puts (2...99999).inject(:*)

завершено в 4.4s (Рубин) и 32.2s в 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.6s и 0.7s для 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.1s

правка 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.7s

изменить 4 как заявленная @Marco13 самая большая проблема заключалась в создании строки, а не в самом BigInteger..

  • BigInteger:3.0s
  • MutableBigInteger hack:10.1s
  • создание строки: ~20s

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:

теперь я понимаю, что это все анекдотические и что мы только измеряем добавление здесь, но я должен сказать, что фраза huldra catch "превосходящая BigInteger с 2015 года" кажется довольно точной в этом случае.

любые комментарии с указателями на потенциальных кандидатов для более быстрых алгоритмов сложения big int очень ценятся.


другие ответы связаны с настройкой производительности с помощью кода.

Если вы используете версию java менее 1.8.051, вы можете настроить большую целочисленную производительность, используя следующие параметры команды:

-XX:-UseMontgomerySquareIntrinsic
-XX:-UseMontgomeryMultiplyIntrinsic
-XX:-UseSquareToLenIntrinsic
-XX:-UseMultiplyToLenIntrinsic

после 1.8.051 эти параметры включены по умолчанию.