A number is a prime when it is divisible by 1 and the number itself. To check if a given number is a prime we can divide the number by primes which are less than the square root of that number. If there is at least one prime which properly divide the number then the number is composite otherwise it is a prime number.
I have written a PrimeGenerator class which produces prime when the nextPrime() method is called. While returning the prime the class also stores that prime into an internal list to use during the later calls to the nextPrime() method.
I have two versions of this class:
- Single-threaded - in this case the prime list divides the number in a single thread.
- Multithreaded - in this case the prime list get split into sub-lists and every sub-list operate division on that number in different threads. The number of sub-lists and the number of threads is equals to the number of cpu core available.
Single-threaded version:
public class PrimeGeneratorSingleThreaded { private List<Integer> primes = new ArrayList<>(Arrays.asList(2, 3)); private int lastIndex = -1; public PrimeGeneratorSingleThreaded() { } public Integer nextPrime() { if (++lastIndex < primes.size()) { return primes.get(lastIndex); } else { int n = primes.get(primes.size() - 1) + 2; while (true) { boolean primeFound = true; int finalN = n; int sqrt = (int) Math.sqrt(n) + 1; Optional<Integer> optional = primes.stream().filter((Integer i) -> i < sqrt).filter((Integer i) -> (finalN % i) == 0).findFirst(); if (optional.isPresent()) { primeFound = false; } if (primeFound) { primes.add(n); break; } n += 2; } return primes.get(lastIndex); } } } Multi-threaded version:
public class PrimeGeneratorMultiThreaded { int nbCores = Runtime.getRuntime().availableProcessors(); private List<Integer> primes = new ArrayList<>(Arrays.asList(2, 3)); private int lastIndex = -1; private List<List<Integer>> primePartitions = new ArrayList<>(); private int lastAdded = 0; public PrimeGeneratorMultiThreaded() { IntStream.range(0, nbCores).forEach(i -> primePartitions.add(new ArrayList<>())); primePartitions.get(0).addAll(primes); } public Integer nextPrime() { if (++lastIndex < primes.size()) { return primes.get(lastIndex); } else { AtomicBoolean compositeFound = new AtomicBoolean(false); int n = primes.get(primes.size() - 1) + 2; while (true) { ExecutorService service = Executors.newFixedThreadPool(nbCores); List<Future<?>> workers = new ArrayList<>(); int finalN = n; primePartitions.forEach((List<Integer> list) -> workers.add(service.submit(new DivisionWorker(compositeFound, new Integer(finalN), list)))); workers.forEach((Future<?> worker) -> { try { worker.get(); } catch (Exception ignore) { } }); if (compositeFound.get()) { n += 2; compositeFound.set(false); try { service.shutdownNow(); } catch (Exception ignore) { } } else { primes.add(n); if (++lastAdded == nbCores) { lastAdded = 0; } primePartitions.get(lastAdded).add(n); try { service.shutdownNow(); } catch (Exception ignore) { } break; } } return primes.get(lastIndex); } } private class DivisionWorker implements Runnable { private AtomicBoolean compositeFound; private int num; private List<Integer> list; private DivisionWorker(AtomicBoolean compositeFound, int num, List<Integer> list) { this.compositeFound = compositeFound; this.num = num; this.list = list; } @Override public void run() { int sqrt = (int) Math.sqrt(num) + 1; Optional<Integer> optional = list.parallelStream().filter((Integer i) -> i < sqrt).filter((Integer i) -> compositeFound.get() || (num % i) == 0).findFirst(); if (optional.isPresent()) { compositeFound.set(true); } } } } Following is the code snippet to test the generation of first 999 primes:
public class Test { public static void main(String[] args) { final PrimeGeneratorSingleThreaded primeGeneratorSingleThreaded = new PrimeGeneratorSingleThreaded(); final PrimeGeneratorMultiThreaded primeGeneratorMultiThreaded = new PrimeGeneratorMultiThreaded(); IntStream.range(1, 1000).forEach(i -> { long startTime1 = System.nanoTime(); long n = primeGeneratorSingleThreaded.nextPrime(); long executionTime1 = System.nanoTime() - startTime1; System.out.println(String.format("Single=> Prime: %1$d. Execution time: %2$d ns.", n, executionTime1)); long startTime2 = System.nanoTime(); n = primeGeneratorMultiThreaded.nextPrime(); long executionTime2 = System.nanoTime() - startTime2; System.out.println(String.format("Multi=> Prime: %1$d. Execution time: %2$d ns.", n, executionTime2)); System.out.println("==================================\n"); }); } } The problem
I have uploaded a gist there you can see the multithreaded version is taking more time than that of the single-threaded version. I was expecting that the multithreaded version would be faster, but it is not. Why is this happening?