简体   繁体   English

Java是否可以优化循环中的“变异” BigInteger操作?

[英]Can Java optimize “mutating” BigInteger operations in loops?

I need to deal with a lot of big numbers much larger than a long (>10^200), so I'm using BigIntegers. 我需要处理很多比长整数(> 10 ^ 200)大得多的数字,所以我正在使用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. 当然,为破坏性行为而制作副本是很浪费的(好吧,只要有足够大的缓冲区可用),所以我想知道Java是否可以优化它(我听说math.java没有公开MutableBigInteger类)还是我应该只编写自己的BigInteger类。

Yes, there is a java.math.MutableBigInteger class that is used by BigInteger for compute-intensive operations. 是的, BigInteger使用了一个java.math.MutableBigInteger类来进行计算密集型操作。 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. Apache Commons库中还有一个“ MutableBigInteger”类,但这只是BigInteger的可变包装器,对您没有帮助。

I was wondering if Java can optimize this somehow ... 我想知道Java是否可以通过某种方式优化它...

No ... not withstanding the above. 不。

or whether I should just write my own BigInteger class. 还是我应该只编写自己的BigInteger类。

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. 另一个方法是下载OpenJDK源代码,找到java.math.MutableBigInteger的源代码,更改其包名称和访问权限,并将其合并到您的代码库中。 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. 唯一的问题是OpenJDK是根据GPL(我认为是GPL-2)获得许可的,如果您曾经使用修改后的类分发代码,那么这将产生影响。

See also: 也可以看看:

A quicker solution is to circumvent the java package visibility. 更快的解决方案是规避Java包的可见性。 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: 您可以通过在自己的项目中创建一个名为java.math的包并创建一个公开类来公开该包私有MutableBigInteger的公共类,如下所示:

package java.math;

public class PublicMutableBigInteger extends MutableBigInteger {

}

Then you can just import java.math.PublicMutableBigInteger; 然后,您可以只导入java.math.PublicMutableBigInteger;。 and use it as any other class. 并将其用作其他任何类。 This solution is quick and doesn't impose upon you any particular licence. 该解决方案非常快捷,不会给您任何特别的许可证。

There's not a lot the compiler can do, because it can't know what the add method does. 编译器无能为力,因为它不知道add方法的作用。 Here is the generated code for the loop's body. 这是为循环主体生成的代码。 As you can see, it simply calls add and stores the result. 如您所见,它仅调用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

In theory, the Java virtual machine run time system could be more clever. 从理论上讲,Java虚拟机运行时系统可能更聪明。 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)

Java won't do any special optimisations for this case. Java在这种情况下不会做任何特殊的优化。 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). 通常,BigInteger与其他类一样被视为普通类(例如,与String不同,在连接多个字符串时,有时会得到一些特殊的优化)。

But in most cases, BigInteger is fast enough that it won't matter anyway. 但是在大多数情况下,BigInteger足够快,因此无论如何都不会造成问题。 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. 如果添加BigIntegers确实是您的瓶颈,那么使用自定义可变的big-integer类充当累加器可能很有意义。 But I wouldn't do this before you've proved that this is genuinely the main bottleneck. 但是在您证明这确实是主要瓶颈之前,我不会这样做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM