使用Java 8的整数列表的总和

时间:2014-02-21 17:07:13

标签: java biginteger java-8

我正在玩Java 8,并使用程序比较Java 6中的一些东西来计算大型列表中偶数的总和

Java 8

public class ListOperationsJava8 {

    static List<BigInteger> list = new LinkedList<>();

    public static void main(String[] args) {
        createList();

        long start = System.currentTimeMillis();

        /*System.out.println(list.parallelStream().
                filter(n -> n.mod(new BigInteger("2")).equals(BigInteger.ZERO)).
                        mapToInt(BigInteger::intValue).sum());  --> gives result        -1795017296 */

        System.out.println(list.parallelStream().
                filter(n -> n.mod(new BigInteger("2")).equals(BigInteger.ZERO)).
                    mapToLong(BigInteger::longValue).sum());

        long end = System.currentTimeMillis();

        System.out.println("Time taken using Java 8:  " + (end - start) + " ms");
    }

    private static void createList() {
        for (int i = 0; i < 100000; i++) {
            list.add(new BigInteger(String.valueOf(i)));
        }
    }
}

Java 6

public class ListOperationsClassic {

    static List<BigInteger> list = new LinkedList<BigInteger>();

    public static void main(String[] args) {
        createList();

        long start = System.currentTimeMillis();

        BigInteger sum = BigInteger.ZERO;

        for(BigInteger n : list) {
            if(n.mod(new BigInteger("2")).equals(BigInteger.ZERO))
                sum = sum.add(n);
        }

        System.out.println(sum);

        long end = System.currentTimeMillis();

        System.out.println("Time taken using Java 6: " + (end - start) + " ms");
    }

    private static void createList() {
        for (int i = 0; i < 100000; i++) {
            list.add(new BigInteger(String.valueOf(i)));
        }
    }
}

我有两个问题

  1. 在Java 8代码中,我最初使用过 mapToInt(BigInteger::intValue).sum())减少价值,但是 结果为负-1795017296!为什么?预期结果2499950000 本身在我可以用int表示的范围内 理解。
  2. 我多次运行代码,我总是在Java 8中找到代码 比使用Java 6的代码花费的时间多5倍。那是什么 可能意味着?对于这种规模的操作,不值得使用 parallelStream和/或reduce操作和普通旧for循环 更好吗?
  3. 以下是其中一项结果:

    2499950000
    Time taken using Java 6: 52 ms
    
    2499950000
    Time taken using Java 8:  249 ms
    

3 个答案:

答案 0 :(得分:6)

这是我快速而肮脏的基准测试,允许在每次测试之间进行JIT热身和GC测试。结果:

  • for loop:686 ms
  • lamdbda:681 ms
  • parallel lambda:405 ms

请注意,我已修改您的代码以使三个测试尽可能相等 - 特别是对于lambdas,我使用缩减来添加BigIntegers而不是转换为long。
我还在每次迭代中删除了不必要的new BigInteger("2")创建。

public class Test1 {

    static List<BigInteger> list = new LinkedList<>();
    static BigInteger TWO = new BigInteger("2");

    public static void main(String[] args) {
        createList();

        long sum = 0;

        //warm-up
        for (int i = 0; i < 100; i++) {
            sum += forLoop().longValue();
            sum += lambda().longValue();
            sum += parallelLambda().longValue();
        }

        {
            System.gc();
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) sum += forLoop().longValue();
            long end = System.currentTimeMillis();
            System.out.println("Time taken using for loop:  " + (end - start) + " ms");
        }

        {
            System.gc();
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) sum += lambda().longValue();
            long end = System.currentTimeMillis();
            System.out.println("Time taken using lambda:  " + (end - start) + " ms");
        }

        {
            System.gc();
            long start = System.currentTimeMillis();
            for (int i = 0; i < 100; i++) sum += parallelLambda().longValue();
            long end = System.currentTimeMillis();
            System.out.println("Time taken using parallelLambda:  " + (end - start) + " ms");
        }
    }

    private static void createList() {
        for (int i = 0; i < 100000; i++) {
            list.add(new BigInteger(String.valueOf(i)));
        }
    }

    private static BigInteger forLoop() {
        BigInteger sum = BigInteger.ZERO;

        for(BigInteger n : list) {
            if(n.mod(TWO).equals(BigInteger.ZERO))
                sum = sum.add(n);
        }
        return sum;
    }

    private static BigInteger lambda() {
        return list.stream().
                filter(n -> n.mod(TWO).equals(ZERO)).
                reduce(ZERO, BigInteger::add);
    }

    private static BigInteger parallelLambda() {
        return list.parallelStream().
                filter(n -> n.mod(TWO).equals(ZERO)).
                reduce(ZERO, BigInteger::add);
    }
}

答案 1 :(得分:5)

对于您的第一个问题:您的值可能大于Integer.MAX_VALUE,因此溢出,这意味着它将从外部表示为从Integer.MIN_VALUE开始。 溢出是此处的关键字。

对于第二部分,我不确切地知道它为什么会有所不同,最终可能会在字节码中找到,但这真的是一个问题,还是过早优化的情况?如果它是第一个,那么你应该担心,如果是第二种情况,那么你就不应该注意它的速度较慢。

我从你的代码中观察到的一个区别是:

  • Java 6:您使用一个BigInteger sum,并在其上调用.add()方法。
  • Java 8:您使用了一些long变量,并在内部将该变量添加到。

此外,您在这里使用parallelStream()进行非常快速的计算,此可能导致创建新底层线程的实际成本比使用正常情况花费更多时间的问题线性stream()

此外,如果您真的在测量速度,那么您应该进行更多的测试,而不是每个测试用例运行一次,时间也可能取决于其他因素 - JVM,CPU的时钟速度等。

作为上一次编辑,您的Java 8代码实际上并不代表您的Java 6代码,它应该是:

final BigInteger sum = BigInteger.ZERO;
list.stream()
.filter(n -> n.mod(new BigInteger("2")).equals(BigInteger.ZERO))
.forEach(n -> { sum = sum.add(n); });

完全代表您的Java 6代码,请注意sum的引入在这里并不是一件好事,如果您使用的话,此代码根本不起作用它用于并行计算。

最后一次编辑,正确显示它应该如何在Java 8中完成,看起来是正确的,适用于并行版本,甚至可以在线性版本中获得额外的性能:

    Optional<BigInteger> sum = list.stream()
            .filter(n -> n.mod(new BigInteger("2")).equals(BigInteger.ZERO))
            .reduce((n1, n2) -> n1.add(n2));
    System.out.println(sum.get());

我在这里使用reduce()运算符,该运算符以BinaryOperator<T>为参数,我用它来将BigInteger加在一起。

有一点需要注意的是,它带有一个事实,即流可能是空的,因此它不知道是否有值,因此返回Optional<T>

请注意,通常您需要致电sum.isPresent()以检查其是否确实有值,但我们知道必须有!list.isEmpty()的值,因此我们直接致电sum.get()

最后,我在PC上测试了不同版本的100万个数字:

  • Java 6:大约190~210ms。
  • Java 8你的代码:大约160~220ms。
  • Java 8,我的线性:大约180~260ms。
  • Java 8,我的并行:大约180~270ms。

所以,由于这不是真正适当的微基准测试,我认为结论是你用于这种目的并不重要。

答案 2 :(得分:2)

你可以简单地调用reduce直接在bigdecimal中添加,而不是转换为long。

monthly_agg.xs('sum', axis=1, level=1)
Out: 
                   d1    d2     d3     d4
city date                                
dfw  2014-05-31  4.06  6.54   2.40  -4.06
pdx  2014-05-31  9.96  6.66  30.35  64.24
sfo  2014-05-31  4.26  6.58   2.32  -4.87
yvr  2014-05-31  2.90  6.64  24.78 -50.55
yyz  2014-05-31  2.90  6.64  24.78 -50.55