Home > database >  Parallel, ordered stringbuilding in Java is being less efficient than sequential implementation
Parallel, ordered stringbuilding in Java is being less efficient than sequential implementation

Time:01-04

I have an int[][] matrix which updates continually in a while{} loop. After each iteration, I want to print the new values of the matrix to the CLI. I'm writing a parallel version of this loop, so I need to print somehow in parallel. Obviously the order of printing matters... But I'm having issues in testing with the current implementation.It's just too slow and I don't know what to do.

The while loop:

int numbOfBlocks = Runtime.getRuntime().availableProcessors();
int blockSize = matrix.length/numbOfBlocks;
int endRow;

while(true) {
   // print matrix to CLI

   StringBuilder stringBuilder = new StringBuilder();
   CountDownLatch stringBuilderLatch = new CountDownLatch(numbOfBlocks);

   for (int i = 0; i < numbOfBlocks; i  ) {
      endRow = (i == numbOfBlocks - 1) ? rows : (i 1) * blockSize;
      StringBuilderThread stringBuilderThread =
            new StringBuilderThread(matrix, i*blockSize, endRow, stringBuilderLatch, stringBuilder, i, numbOfBlocks);
      executor.execute(stringBuilderThread);
   }
   stringBuilderLatch.await();

   stringBuilder.append("___");
   System.out.println(stringBuilder.toString());
}

The StringBuilderThread:

import java.util.concurrent.CountDownLatch;

public class StringBuilderThread implements Runnable {

    private int[][] matrixUpd;
    private int startRow;
    private int endRow;
    private CountDownLatch latch;
    private StringBuilder mainStringBuilder;
    private int order;
    private int numbOfBlocks;

    public StringBuilderThread(int[][] matrixUpd,
                               int startRow,
                               int endRow,
                               CountDownLatch latch,
                               StringBuilder mainStringBuilder,
                               int order,
                               int numbOfBlocks) {
        this.matrixUpd = matrixUpd;
        this.startRow = startRow;
        this.endRow = endRow;
        this.latch = latch;
        this.mainStringBuilder = mainStringBuilder;
        this.order = order;
        this.numbOfBlocks = numbOfBlocks;
    }
    @Override
    public void run() {
        StringBuilder tempStringBuilder = new StringBuilder();
        int cols = matrixUpd[0].length;
        for (int i = startRow; i < endRow; i  ) {
            tempStringBuilder.append("|");
            for (int j = 0; j < cols; j  ) {
                if(matrixUpd[i][j] == 1) {
                    tempStringBuilder.append("*  ");
                } else {
                    tempStringBuilder.append(".  ");
                }
            }
            tempStringBuilder.append("|\n");
        }
        tempStringBuilder.append(" ");

        // order synchronization, so that threads write to stringbuilders in correct order
        long latchCount = latch.getCount();
        while (!(numbOfBlocks-latchCount == order)) {
            latchCount = latch.getCount();
        }
        mainStringBuilder.append(tempStringBuilder.toString());
        latch.countDown();
    }
}

The issue is, this implementation is actually slower than just printing to CLI sequentially. I suspect the latch is the issue, as multiple threads keep trying to access it, so the threads that actually need it have to wait.

Everything else in my program runs 2x-3x faster in parallel, except this, and it bottlenecking my program.

EDIT:

I tested 3 solutions:

  1. Sequential printing
  2. Parallel stringbuilding with Callables, then printing the string
  3. Parallel stringbuilding with array streams, as suggested by the accepted answer

The results were tested using matrices of dimentions:

  1. 200x200, for 100 iterations
  2. 1000x1000, for 100 iterations
  3. 2000x2000, for 200 iterations
  4. 4000x4000, for 200 iterations

Array parallel streams had the best performance in every test and were the simplest to implement.

Thank you for your help.

CodePudding user response:

You really shouldn't expect to see a performance increase for small matrices. There is some overhead involved by using threads and you should have some proper benchmarks to measure any performance changes.

In any case, if you want to try parallelizing your operations, you can try out parallel streams to map your int[] rows to strings. According to the package summary and the specification of Stream#collect that should not change the result as the Collector returned by Collectors.joining is not concurrent.

Except for operations identified as explicitly nondeterministic, such as findAny(), whether a stream executes sequentially or in parallel should not change the result of the computation.

If the stream is parallel, and the Collector is concurrent, and either the stream is unordered or the collector is unordered, then a concurrent reduction will be performed (see Collector for details on concurrent reduction.)

final int[][] matrix = {
        {1, 0, 1, 0},
        {0, 0, 0, 1}
};

String collect = Arrays.stream(matrix).parallel()
        .map(ints -> Arrays.stream(ints)
                .mapToObj(value -> value == 1 ? "*" : ".")
                .collect(Collectors.joining(" ", "|", "|"))
        )
        .collect(Collectors.joining("\n"));
System.out.println(collect);

OUTPUT:

|* . * .|
|. . . *|
  • Related