ellis
ellis

Reputation: 311

Nested loops with using Streams to achieve best performance

I started learning Java 8 features, streams and lambdas in particular, and I'm quite confused. I tried to rewrite the piece of code below

MyClass graph = new MyClass(V);

for (int i = 0; i < numOfVertices; i++) {
   for (int j = 0; j < numOfVertices; j++) {
      if (adjMatrix[i][j] != 0) {
         graph.addEdge(i, j, adjMatrix[i][j]);
      }
   }
}

I wrote this:

int b = IntStream.range(0, numOfVertices - 1).parallel()
   .map(i -> (IntStream.range(0, numOfVertices - 1))
      .map(j -> {
         if (adjMatrix[i][j] != 0) {
            graph.addEdge(i, j, adjMatrix[i][j]);
         }
      }));

Of course it doesn't work. What would be the proper way to rewrite the code above? I'm also looking for the best performance, so I attempted to use the parallel method.

Thanks!

Upvotes: 0

Views: 1418

Answers (2)

lczapski
lczapski

Reputation: 4120

Short answer

If you want "best performance", the shorter time in iteration: Use "for loop" for small matrix, for bigger cases you can consider use "parallel streams"

.. long answer

I created a test to measure the average time of execution one of three options.

"For loop:"

for (int i = 0; i < size; i++) {
    for (int j = 0; j < size; j++) {
        doSomething(numOfVertices[i][j]);
    }
}

"IntStream.range:"

IntStream.range(0, size)
        .forEach(i -> (IntStream.range(0, size))
                .forEach(j -> {
                    doSomething(numOfVertices[i][j]);
                }));

"IntStream.range parallel:"

IntStream.range(0, size).parallel()
        .forEach(i -> (IntStream.range(0, size))
                .forEach(j -> {
                    doSomething(numOfVertices[i][j]);
                }));

This section was removed as I could not simpulate it's time cost:

      if (adjMatrix[i][j] != 0) {
         graph.addEdge(i, j, adjMatrix[i][j]);
      }

Below is the whole test code that perform measurement for different matrix. It runs every case 10000 times and gives the average time. Here are the results for run on my computer. For small matrix (form 1 x 1 to 100 x 100) for loop is the best option. When matrix is big (more than 500 x 500) using "IntStream.range parallel" is faster. "IntStream.range" is always worse than "For loop" for every case.

Size: 1
IntStream.range parallel: 0.002300
         IntStream.range: 0.002200
                For loop: 0.000000
Size: 5
IntStream.range parallel: 0.023000
         IntStream.range: 0.000500
                For loop: 0.000000
Size: 10
IntStream.range parallel: 0.025800
         IntStream.range: 0.000300
                For loop: 0.000100
Size: 50
IntStream.range parallel: 0.042200
         IntStream.range: 0.004300
                For loop: 0.000900
Size: 100
IntStream.range parallel: 0.035000
         IntStream.range: 0.006000
                For loop: 0.004300
Size: 500
IntStream.range parallel: 0.060200
         IntStream.range: 0.102000
                For loop: 0.079600
Size: 1000
IntStream.range parallel: 0.122000
         IntStream.range: 0.379800
                For loop: 0.347400
    public static void main(String[] args) {

        Arrays.asList(1, 5, 10, 50, 100, 500, 1000).forEach(
                size -> {
                    System.out.println("Size: " + size);
                    Integer[][] numOfVertices = createArray(size);

                    Map<String, Runnable> toRun = Map.of(
                            "For loop:",
                            () -> {
                                for (int i = 0; i < size; i++) {
                                    for (int j = 0; j < size; j++) {
                                        doSomething(numOfVertices[i][j]);
                                    }
                                }
                            },
                            "IntStream.range:",
                            () -> {
                                IntStream.range(0, size)
                                        .forEach(i -> (IntStream.range(0, size))
                                                .forEach(j -> {
                                                    doSomething(numOfVertices[i][j]);
                                                }));
                            },
                            "IntStream.range parallel:",
                            () -> {
                                IntStream.range(0, size).parallel()
                                        .forEach(i -> (IntStream.range(0, size))
                                                .forEach(j -> {
                                                    doSomething(numOfVertices[i][j]);
                                                }));
                            }
                    );

                    int howManyTimes = 10000;
                    Map<String, Double> map = new HashMap<>();
                    toRun.entrySet().forEach(e -> {
                        double time = (((double)IntStream.range(0, howManyTimes).mapToObj(
                                (x) -> executionTime(e.getValue()))
                                .reduce(Long::sum).get()) / howManyTimes);

                        map.put(e.getKey(), time);
                    });

                    map.entrySet().forEach(e -> {
                        System.out.println(String.format("%25s %f", e.getKey(), e.getValue()));
                    });
                }
        );
    }

    private static Integer[][] createArray(int size) {
        Integer[][] val = new Integer[size][size];
        IntStream.range(0, size).forEach(
                i -> IntStream.range(0,size).forEach(
                        j -> val[i][j] = (int)(Math.random() * 100)
                )
        );
        return  val;
    }

    private static int doSomething(int val) {
        return val * val;
    }

    private static long executionTime(Runnable execThis)
    {
        long startTime = System.currentTimeMillis();
        execThis.run();
        long endTime = System.currentTimeMillis();
        return (endTime-startTime);
    }

Upvotes: 0

Pavel
Pavel

Reputation: 1647

Regardless to performance this is a simple form rewriting it with stream:

                IntStream.range(0, numOfVertices - 1)
                 .forEach(i -> IntStream.range(0, numOfVertices - 1)
                   .filter(j -> adjMatrix[i][j] != 0)
                     .forEach(j -> graph.addEdge(i, j, adjMatrix[i][j])));

Upvotes: 1

Related Questions