1

Here's my sample program:

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
*/

Notice how much slower parallelSetAll() runs than does setAll(). My guess is that the single random generator is causing all kinds of traffic overhead for the parallel version, but I'm not sure, so first I'd like to understand why this is happening.

How does one create a generator for parallelSetAll() that doesn't make it much slower? I suspect that it will be a function that independently operates on the element, via the passed-in index. Such as n -> ia[n] * 10.

As someone noted, I should point out that this is not a proper micro-benchmark; your mileage may vary. It is intended to be a simple way to get a feel for the way the algorithm works, not something to use for fine-tuning.

Here's the working example:

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
*/

I've simplified things, and also changed the time units to milliseconds.

user1677663
  • 1,431
  • 15
  • 21

1 Answers1

4

Quoting Random JavaDoc:

Instances of java.util.Random are threadsafe. However, the concurrent use of the same java.util.Random instance across threads may encounter contention and consequent poor performance. Consider instead using java.util.concurrent.ThreadLocalRandom in multithreaded designs.

You could thus for example use the suggested ThreadLocalRandom instead, which will most likely provide better performance than the sequential setAll():

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

(Remember, though, that your code sample is not a proper micro-benchmark)

The basic idea when running things in parallel is that you should try to avoid contention, and make parallel computations as independant as possible.

A typical usage of parallelSetAll() would thus compute a value solely based on the passed-in number, or retrieve some value from a source that does not require synchronization for reads, such as another array or collection.

Community
  • 1
  • 1
Didier L
  • 18,905
  • 10
  • 61
  • 103
  • 1
    Of course, if all you want is an int array, filled in parallel with random values, you just need to call `new SplittableRandom().ints(10_000_000).parallel().toArray()`… – Holger Jan 21 '16 at 11:12
  • 1
    Of course. Unless this is the first time you've heard of `SplittableRandom`. That's a pretty nice class, thanks. I wonder what other clever classes there are that haven't popped up on my radar yet. – user1677663 Jan 21 '16 at 16:22