使用Java 8 Arrays.parallelSetAll()的正确方法是什么?

时间:2016-01-20 21:54:33

标签: java concurrency parallel-processing java-8

这是我的示例程序:

import java.util.*;
import java.time.*;

class Randy {
  private Random r;
  // New generator for each instance:
  public Randy() { r = new Random(47); }
  public Integer get(int n) { return  r.nextInt(); }
}

public class ParallelSetAll {
  static int[] ia = new int[10_000_000];
  public static void main(String[] args) {
    Instant start1 = Instant.now();
    Arrays.setAll(ia, new Randy()::get);
    long nanos1 = Duration.between(start1, Instant.now()).toNanos();
    System.out.println(nanos1);

    Instant start2 = Instant.now();
    Arrays.parallelSetAll(ia, new Randy()::get);
    long nanos2 = Duration.between(start2, Instant.now()).toNanos();
    System.out.println(nanos2);
  }
}
/* Output:
223000000
1261000000
*/

注意parallelSetAll()运行速度比setAll()慢多少。我的猜测是单个随机生成器导致并行版本的各种流量开销,但我不确定,所以首先我想了解为什么会发生这种情况。

如何为parallelSetAll()创建不会使其慢得多的生成器?我怀疑它将是一个通过传入索引独立操作元素的函数。例如n -> ia[n] * 10

有人指出,我应该指出,这不是一个适当的微观基准;你的旅费可能会改变。它旨在通过一种简单的方式来了解算法的工作方式,而不是用于微调的方法。

以下是工作示例:

import java.util.*;
import java.util.concurrent.*;
import java.time.*;

public class ParallelSetAll {
  static long timeIt(Runnable test) {
    Instant start = Instant.now();
    test.run();
    long millis =
      Duration.between(start, Instant.now()).toMillis();
    System.out.println(millis);
    return millis;
  }
  static int get(int n) {
    return ThreadLocalRandom.current().nextInt();
  }
  public static void main(String[] args) {
    int[] ia = new int[40_000_000];
    timeIt(() ->
      Arrays.setAll(ia, ParallelSetAll::get));
    timeIt(() ->
      Arrays.parallelSetAll(ia, ParallelSetAll::get));
  }
}
/* Output:
482
198
*/

我简化了一些事情,并将时间单位更改为毫秒。

1 个答案:

答案 0 :(得分:4)

引用Random JavaDoc:

  

java.util.Random的实例是线程安全的。但是,并发   跨线程使用相同的java.util.Random实例可能会遇到   争用和随之而来的糟糕表现。考虑改为使用   多线程设计中的java.util.concurrent.ThreadLocalRandom

例如,您可以使用建议的ThreadLocalRandom,这很可能提供比顺序setAll()更好的性能:

class ThreadLocalRandy {
  public ThreadLocalRandy() {}
  public Integer get(int n) { return ThreadLocalRandom.current().nextInt(); }
}

(但请记住,您的代码示例不是a proper micro-benchmark

并行运行时的基本思想是你应该尽量避免争用,并使并行计算尽可能独立。

parallelSetAll()的典型用法因此将仅根据传入的数量计算值,或从不需要读取同步的源中检索某些值,例如另一个数组或集合。