10

I need to deal with a lot of big numbers much larger than a long (>10^200), so I'm using BigIntegers. The most common operation I perform is adding them to an accumulator, ex:

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

Of course making copies for destructive actions is quite a waste (well, as long as there's a large enough buffer available), so I was wondering if Java can optimize this somehow (I heard there was a MutableBigInteger class not exposed by math.java) or whether I should just write my own BigInteger class.

Cubic
  • 14,902
  • 5
  • 47
  • 92
  • 8
    It can optimize indirectly in quite a few ways: for example it can realize that *most* new instances (except for the last) live very, very shortly and never leave the method and therefore optimize how their memory is allocated. This means that it's *very* hard to predict the performance of this code. Have you *benchmarked* you code and *proven* that this code is the bottleneck? – Joachim Sauer May 18 '12 at 12:36
  • In case you do need to use a mutable Integer class, do take a look at: [package org.apache.commons.lang.mutable](http://commons.apache.org/lang/api-2.4/org/apache/commons/lang/mutable/package-summary.html). – anubhava May 18 '12 at 12:44
  • @anubhava: It's good to be aware of these classes, but I don't see how they would help with this question. – NPE May 18 '12 at 12:57
  • @aix: My comment was in reference to this line in OP's question: `whether I should just write my own BigInteger class.` – anubhava May 18 '12 at 12:59
  • 1
    What is BigNum? It's not a standard Java class... – Puce May 18 '12 at 13:00
  • Questions like this make me want to post a one word answer: "yes" – Cephalopod May 18 '12 at 13:33
  • To go along with @JoachimSauer have you benchmarked to see if this is slowing you down majorly? – bluesman May 18 '12 at 14:45
  • Well, you're right in that the performance of this isn't as much of a problem right now as I initially thought it'd be. I'm still generally interested in the optimizing capabilities of the JVM and/or the java compiler though (specifically the oracle one, though I also use OpenJDK occasionally) – Cubic May 18 '12 at 16:01

4 Answers4

2

Yes, there is a java.math.MutableBigInteger class that is used by BigInteger for compute-intensive operations. Unfortunately, it is declared as package private, so you cannot use it. There is also a "MutableBigInteger" class in the Apache Commons library, but it is just a mutable wrapper for BigInteger and that is no help for you.

I was wondering if Java can optimize this somehow ...

No ... not withstanding the above.

or whether I should just write my own BigInteger class.

That's one approach.

Another is to to download the OpenJDK sources, find the source code for java.math.MutableBigInteger, change its package name and access, and incorporate it into your code-base. The only snag is that OpenJDK is licensed under the GPL (GPL-2 I think), and that has implications if you ever distribute code using the modified class.

See also:

Community
  • 1
  • 1
Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
2

A quicker solution is to circumvent the java package visibility. You can do that by creating a package named java.math in your own project and creating a public class that exposes the package private MutableBigInteger like so:

package java.math;

public class PublicMutableBigInteger extends MutableBigInteger {

}

Then you can just import java.math.PublicMutableBigInteger; and use it as any other class. This solution is quick and doesn't impose upon you any particular licence.

Svetlin Mladenov
  • 4,307
  • 24
  • 31
  • 1
    If you are injecting a new class into `java.math` (which is breaking "the rules") to circumvent the access restriction, you may as well go the whole hog and modify the classes access. Same difference really ... – Stephen C May 19 '12 at 11:16
  • @Stephen C What are you trying to say? I don't understand you? Injecting a new class into java.math is definitely quicker and easier. – Svetlin Mladenov May 19 '12 at 12:07
  • I mean problems like this ... http://stackoverflow.com/questions/860187/access-restriction-on-class-due-to-restriction-on-required-library-rt-jar – Stephen C May 20 '12 at 05:02
2

There's not a lot the compiler can do, because it can't know what the add method does. Here is the generated code for the loop's body. As you can see, it simply calls add and stores the result.

   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

In theory, the Java virtual machine run time system could be more clever. For instance, it could detect that one object continuously overwrites another just allocated, and just swap two allocation buffers for them. However, as we can see by running the following program with garbage collection logging enabled, this is sadly not the case

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();
    }
}

See the garbage collection calls during the addition process.

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)
Diomidis Spinellis
  • 18,734
  • 5
  • 61
  • 83
0

Java won't do any special optimisations for this case. BigInteger is generally treated as a normal class like any other (unlike String, for example, which sometimes gets some special optimisations when you are concatenating many strings).

But in most cases, BigInteger is fast enough that it won't matter anyway. If you really think it might be a problem, I'd suggest profiling you code and working out what is taking the time.

If adding BigIntegers is really your bottleneck, then it might make sense to use a custom mutable big-integer class to act as an accumulator. But I wouldn't do this before you've proved that this is genuinely the main bottleneck.

mikera
  • 105,238
  • 25
  • 256
  • 415