2016-12-29 49 views
4

我正在玩无限的流,并制定了这个程序进行基准测试。基本上你提供的号码越大,它的结束速度就越快。然而,我很惊讶地发现,与顺序流相比,使用parellel流导致性能呈指数级恶化。直观上,人们会期望在多线程环境中生成和评估无限数量的随机数字,但似乎并非如此。为什么是这样?为什么并行流更慢?

final int target = Integer.parseInt(args[0]); 
    if (target <= 0) { 
     System.err.println("Target must be between 1 and 2147483647"); 
     return; 
    } 

    final long startTime, endTime; 
    startTime = System.currentTimeMillis(); 

    System.out.println(
     IntStream.generate(() -> new Double(Math.random()*2147483647).intValue()) 
     //.parallel() 
     .filter(i -> i <= target) 
     .findFirst() 
     .getAsInt() 
    ); 

    endTime = System.currentTimeMillis(); 
    System.out.println("Execution time: "+(endTime-startTime)+" ms"); 
+5

并行化的小任务总是比较慢。多线程有足够的开销,任务需要证明成本,否则你不会看到任何收益。另外,1个测试是没有意义的。至少,坚持这个循环,并取平均水平。 – Carcigenicate

+2

@Carcigenicate除此之外,我相信Math.random()减慢它:-)检查我的答案,如果感兴趣。 –

+0

玩-Djava.util.concurrent.ForkJoinPool.common.parallelism = ,更改公共池并观察结果 – gaston

回答

6

我完全同意其他评论和答案,但实际上你的测试在目标非常低的情况下表现很奇怪。在我的小型笔记本电脑上,平行版本的平均速度比目标低时低60倍。这种极端的差异无法通过流API中并行化的开销来解释,所以我也非常惊讶:-)。 IMO罪魁祸首就在这里:

Math.random() 

内部此调用依赖于java.util.Random全局实例。在documentation of Random中写为:

java.util.Random的实例是线程安全的。但是,跨线程使用同一个java.util.Random实例的并发 可能会遇到 争用以及随之而来的糟糕的性能。请考虑在多线程设计中使用 ThreadLocalRandom。

所以我认为,与顺序执行相比,并行执行的性能非常差,这是由随机线程争用解释的,而不是任何其他开销。如果您使用ThreadLocalRandom(根据文档中的建议),则性能差异不会那么显着。另一种选择是实施更高级的号码供应商。

+0

查看'Math.random()'的内部实现 - 它是用原子完成的,没有任何锁。很难相信它引入了这样的差异 –

+3

Yeap,我刚刚检查过它。但是,与线程本地随机相比,它的确带来了巨大的差异,就像文档所说的那样......:O –

+1

@SergeyFedorov,尽管atomics没有锁定,但他们可以大大降低原子操作的do-while循环中的性能,直到考虑状态是一致的。问题是Unsafe.compareAndSwap() - 他们在里面使用繁忙的等待。在高争用情况下,成本会呈指数级增长,并且一组操作的同步块会更好。只有测试才能说明最佳策略。那么,当然,ThreadLocalRandom每次都会赢得两种方法。 –

0

将工作传递给多个线程的代价在你第一次执行时花费很大。这个成本是相当固定的,所以即使你的任务微不足道,开销也相对较高。

您遇到的问题之一就是非常低效的代码是确定解决方案执行情况的一个非常糟糕的方法。另外,第一次运行的方式以及它在几秒后的运行方式通常可能有100倍的差异(可以更多)。我建议使用一个已经最优化的例子,然后尝试使用多个线程。

例如

long start = System.nanoTime(); 
int value = (int) (Math.random() * (target+1L)); 
long time = System.nanoTime() - value; 
// don't time IO as it is sooo much slower 
System.out.println(value); 

注意:直到代码已经预热并编译完成才会有效。即忽略该代码运行的前2-5秒。

0

根据各种答案的建议,我想我已经修复了它。我不确定具体的瓶颈是什么,但是在i5-4590T上,具有以下代码的并行版本的执行速度比顺序版本更快。为了简便起见,我只包括(重构)代码的相关部分:

static IntStream getComputation() { 
    return IntStream 
      .generate(() -> ThreadLocalRandom.current().nextInt(2147483647)); 
} 

static void computeSequential(int target) { 
    for (int loop = 0; loop < target; loop++) { 
     final int result = getComputation() 
        .filter(i -> i <= target) 
        .findAny() 
        .getAsInt(); 
     System.out.println(result); 
    } 
} 

static void computeParallel(int target) { 
    IntStream.range(0, target) 
       .parallel() 
       .forEach(loop -> { 
        final int result = getComputation() 
         .parallel() 
         .filter(i -> i <= target) 
         .findAny() 
         .getAsInt(); 
        System.out.println(result); 
       }); 
} 

编辑:我还应该注意到,我把它们都放在一个循环来获得更长的运行时间。

+2

这不是一个有效的测试,因为您现在正在另一个并行操作中执行并行流操作,该操作会将内部操作(您的问题的实际操作)基本转变为顺序操作,因为工作被分割为完全不同的方式,即范围'IntStream.range(0,target)'将被并行处理,而不是随机数字序列。 – Holger

+0

@Holger但在多台机器上测试之后,我仍然得到了我期望的结果。在四核CPU上,对于大多数1000以下的数字,并行版本的执行速度比顺序版本快3倍以上。 –

+1

其实,我尝试将.parallel()添加到顺序版本,并且它仍然较慢,所以加速是来自InstStream.range而不是随机数字流。 –