2014-02-21 30 views
3

我只是与Java 8玩弄和使用程序来计算的大名单琛使用Java 8

甚至数之和的Java 8与Java 6比较几件事整数列表

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表示的范围内,如I 所理解的。
  2. 我跑了几次代码,我总是发现Java 8中的代码比使用Java 6的代码多5倍。Java代码 可能意味着什么?对于这种规模的操作,这是不值得使用 parallelStream和/或reduce操作和普通老式循环 是更好?

下面是结果之一:

2499950000 
Time taken using Java 6: 52 ms 

2499950000 
Time taken using Java 8: 249 ms 
+4

_“总和值2499950000本身是可以通过我的理解一个int来表示的范围内。” _不,一个int的最大值是2147483647这是少一些。 –

+0

尝试在没有调用System.out.println(list.parallelStream()...)的情况下运行它,这会增加执行时间。 – Bucket

+1

并行流具有足够的开销,并且仅在更大的迭代次数(每个元素的持续时间),然后可以并行完成,如果你调用Thread.sleep(100),统计数据将会恢复 –

回答

6

这是我的快速和肮脏的基准,允许每次测试之间的JIT预热和GC-ing。结果:

  • for循环:686毫秒
  • lamdbda:681毫秒
  • 平行拉姆达:405毫秒

请注意,我已经修改了代码,使这三个测试,等同可能 - 特别是对于lambda表达式,我使用的是减少添加BigIntegers而不是转换为long。
我也在每次迭代中删除了不必要的创建。

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); 
    } 
} 
+0

谢谢(+1),会测试它。据我所知,你用Jdk 8的lambda和for循环版本? – zencv

+0

是的所有测试jdk8 - 但我不认为它会产生重大差异。 – assylias

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

最后,我以1万个号码测试我的电脑上不同的版本:

  • 的Java 6:大约190〜210ms。
  • Java 8你的代码:大约160〜220ms。
  • Java 8,线性:大概180〜260ms。
  • Java 8,矿井平行:大概180〜270ms。

所以,这是不是真的合适microbenchmarking,我认为结论是,它并没有真正不管你用什么为这种目的。

+0

感谢(+1)对于显示可选和减少,一定会尝试一下。 – zencv

0

您可以直接调用reduce直接在bigdecimal中添加而不是转换为long。

list.parallelStream() 
.filter(n -> n.mod(new BigInteger("2")).equals(BigInteger.ZERO)) 
.reduce(BigDecimal.ZERO, BigDecimal::add)