0

this is a continuation of this post. I must calculate many times some statistics(Max, mean, min, median and std dev) of arrays and I have a performance issue given the sort of my arrays in the method calcMaxMinMedian.

Given, I could not improve much further the summary statistics of an array performance. I am trying now to understand strategies and work arounds to parallelize my upper calls or any other smart thoughts.

I have seen this doc but I am not familiar As well as this (post)[https://stackoverflow.com/questions/20375176/should-i-always-use-a-parallel-stream-when-possible/20375622].

I tried using parallel streams, however probably given my SharedResource, the actual performance using the for loop was worse.

Time (s) functionUsingForLoop173
Time (s) functionUsingParallelStream194

Do anyone have an idea of what could I try to parallelize or any other thoughts on how to improve the overrall performance? Here is what I tried:

public class MaxMinMedianArrayUtils {
    int[] sharedStaticResource={1,5,5};//Shared resource across

    /**
     * Return an array with summary statistics. Max, mean,std dev,median,min.
     * Throw an IllegalArgumentException if array is empty.
     *
     * @param a array.
     * @return array returning Max(0), mean(1),std dev(2),median(3),min(4) in
     * respective
     * positions.
     */
    public static double[] getSummaryStatistics(double[] a) {
        double[] summary = new double[5];
        if (a.length == 0) {
            throw new IllegalArgumentException(
                    "Array is empty, please " + "verify" + " the values.");
        } else if (a.length == 1) {
            summary[0] = a[0];
            summary[1] = a[0];
            summary[2] = 0;
            summary[3] = a[0];
            summary[4] = a[0];
        } else {
            double[] meandStd = calcMeanSDSample(a);
            summary[1] = meandStd[0];//Mean
            summary[2] = meandStd[1];//Standard Deviation
            double[] maxMinMedian = calcMaxMinMedian(a);
            summary[0] = maxMinMedian[0];//Max
            summary[4] = maxMinMedian[1];//Min
            summary[3] = maxMinMedian[2];//Median
        }
        return summary;
    }

    public static double[] calcMeanSDSample(double numArray[]) {
        int length = numArray.length;
        double[] meanStd = new double[2];
        if (length == 0) {
            throw new IllegalArgumentException(
                    "Array is empty, please " + "verify" + " the values.");
        } else if (length == 1) {
            meanStd[0] = numArray[0];
            meanStd[1] = 0.0;
        } else {
            double sum = 0.0, standardDeviation = 0.0;

            for (double num : numArray) {
                sum += num;
            }

            meanStd[0] = sum / length;

            for (double num : numArray) {
                standardDeviation += Math.pow(num - meanStd[0], 2);
            }
            meanStd[1] =
                    Math.sqrt(standardDeviation / ((double) length - 1.0));//-1
            // because it is
            // for sample
        }
        return meanStd;
    }

    public static double[] calcMaxMinMedian(double[] a) {
        double[] maxMinMedian = new double[3];
        if (a.length == 0) {
            throw new IllegalArgumentException(
                    "Array is empty, please " + "verify" + " the values.");
        } else if (a.length == 1) {
            for (int i = 0; i < 3; i++) {
                maxMinMedian[i] = a[0];
            }
        } else {
            Arrays.sort(a);
            maxMinMedian[0] = a[a.length - 1];
            maxMinMedian[1] = a[0];
            maxMinMedian[2] = (a.length % 2 != 0) ? (double) (a[a.length / 2]) :
                    (double) ((a[(a.length - 1) / 2] + a[a.length / 2]) / 2.0);
        }
        return maxMinMedian;
    }

    public static void main(String[] args) {
        int numVals = 1000;
//        double[] ar = new double[numVals];
        int numCalculations = 2 * 1000 * 1 * 1000;
//        int numCalculations = 2 * 1000;
        MaxMinMedianArrayUtils maxMinMedianArrayUtils=
                new MaxMinMedianArrayUtils();
        Instant start = Instant.now();
        double[][] statsPerCalculation=
                maxMinMedianArrayUtils.functionUsingForLoop(numVals,
                numCalculations);
        Instant end = Instant.now();
        long totalTime = Duration.between(start, end).toSeconds();
        System.out.println("Time (s) functionUsingForLoop" + totalTime);

        Instant start3 = Instant.now();
        double[][] statsPerCalculation3=
                maxMinMedianArrayUtils.functionUsingParallelStream(numVals,
                        numCalculations);
        Instant end3 = Instant.now();
        long totalTime3 = Duration.between(start3, end3).toSeconds();
        System.out.println("Time (s) functionUsingParallelStream" + totalTime3);

    }

    private double[][] functionUsingForLoop(int numVals,
                                  int numCalculations) {
        // calculations that is used to get some values, but is not modified.
        double[][] statsPerCalculation= new double[numCalculations][5];//Each
        // line
        // stores
        // the stats of the array generated in the numCalculations loop
        for (int i = 0; i < numCalculations; i++) {//Complete independent
            // calculations that I want to parallelize
            double[]array=functionSimulateCalculations(numVals);
            double[] stats = getSummaryStatistics(array);
            for(int s = 0; s < stats.length; s++) {//Copy
                statsPerCalculation[i][s] = stats[s];
            }
        }
        return statsPerCalculation;
    }

    private double[][] functionUsingParallelStream(int numVals,
                                           int numCalculations) {
        // calculations that is used to get some values, but is not modified.
        double[][] statsPerCalculation= new double[numCalculations][5];//Each
        // line
        // stores
        // the stats of the array generated in the numCalculations loop
        double[][] finalStatsPerCalculation = statsPerCalculation;
        IntStream.range(0,numCalculations).parallel().forEach((i)->{
                    double[] array=functionSimulateCalculations(numVals);
                    double[] stats = getSummaryStatistics(array);
                    for(int s = 0; s < stats.length; s++) {
                        finalStatsPerCalculation[i][s] = stats[s];
                    }
                }
        );
        return statsPerCalculation;
    }

    private double[] functionSimulateCalculations(int numVals) {
        double[] ar=new double[numVals];
        for (int k = 0; k < numVals; k++) {//To simulate the
            // actual function of my
            // use case
            ar[k] = Math.random()*sharedStaticResource[0];
        }
        return ar;
    }

} // Utility

DTK
  • 95
  • 1
  • 9
  • An easy improvement would be using [Arrays.parallelSort](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Arrays.html#parallelSort(double%5B%5D)) instead of Arrays.sort. – VGR Feb 06 '22 at 17:04

1 Answers1

1

Part of your issue is that you are computing your randomised the data samples inside the tests, but have contention with the singleton random number generation in parallel threads. Also this means that you have no means of validating that the parallel algorithm matches the serial results.

Refactor your tests so that inputs are pre-computed once - you don't care about timing this step:

private double[][] generateInputData(int numVals, int numCalculations) {
    // calculations that is used to get some values, but is not modified.
    double[][] inputs = new double[numCalculations][];// Each
    for (int i = 0; i < numCalculations; i++) {
        inputs[i] = functionSimulateCalculations(numVals);
    }
    return inputs;
}

Then you can run both tests on same inputs:

private double[][] functionUsingForLoop(double[][]arrays) {
    // calculations that is used to get some values, but is not modified.
    int numCalculations = arrays.length;
    double[][] statsPerCalculation = new double[numCalculations][5];// Each
    for (int i = 0; i < numCalculations; i++) {
        double[] stats = getSummaryStatistics(arrays[i]);
        for (int s = 0; s < stats.length; s++) {
            statsPerCalculation[i][s] = stats[s];
        }
    }
    return statsPerCalculation;
}
private double[][] functionUsingParallelStream(double[][]arrays) {
    int numCalculations = arrays.length;
    double[][] statsPerCalculation = new double[numCalculations][5];// Each
    double[][] finalStatsPerCalculation = statsPerCalculation;
    IntStream.range(0, numCalculations).parallel().forEach((i) -> {
        double[] stats = getSummaryStatistics(arrays[i]);
        for (int s = 0; s < stats.length; s++) {
            finalStatsPerCalculation[i][s] = stats[s];
        }
    });
    return statsPerCalculation;
}

Finally make your main() do some warmups, initialise the arrays, time each section and compare the results:

for (int numCalculations : new int[] { 1, 2, 8, 8, 2 * 1000, 2* 10000, 2*1000*1*1000 } ) {
    double[][] arrays = maxMinMedianArrayUtils.generateInputData(numVals, numCalculations);
    // ...
    double[][] statsPerCalculation= maxMinMedianArrayUtils.functionUsingForLoop(arrays);
    // ...
    double[][] statsPerCalculation3= maxMinMedianArrayUtils.functionUsingParallelStream(arrays);
    // ...
    // COMPARE the results
    if (statsPerCalculation3.length != statsPerCalculation.length)
        throw new RuntimeException("Results not same!");
    for (int i = statsPerCalculation3.length - 1; i >= 0; i--) {
       // System.out.println("statsPerCalculation ["+i+"]="+Arrays.toString(statsPerCalculation[i]));
       // System.out.println("statsPerCalculation3["+i+"]="+Arrays.toString(statsPerCalculation3[i]));
        for (int v = statsPerCalculation3[i].length - 1; v >= 0; v--) {
            if (Math.abs(statsPerCalculation3[i][v]-statsPerCalculation[i][v]) > 0.0000000000001)
                throw new RuntimeException("Results not same at ["+i+"]["+v+"]");
        }
    }
}

At this point, you'll see quite different trend in the results, parallel stream version a lot quicker than non-parallel.

DuncG
  • 12,137
  • 2
  • 21
  • 33
  • Genius, thank you very much! Indeed I was not profiling correctly. Would you say there apart from the parallel stream there is another technique that I can use to tackle the performance issue? – DTK Feb 27 '22 at 13:16