import java.util.function.DoubleUnaryOperator; import java.util.stream.IntStream; /** * This program compares execution times for an algorithm implemented * three different ways: Using a basic for loop, using Java's stream * API with a sequential stream, and using Java's stream API with * a parallel stream. (The problem that is solved is to compute * the value of a Riemann sum, but that's not the point of the program. * (Results will depend on the machine where the program is run. * The for loop is expected to be faster than a sequential stream. * On a multi-core machine, the parallel stream should be faster than * the for loop when the number of subintervals in the sum is large, * but exactly how large will vary.) */ public class RiemannSumStreamExperiment { /** * Use a basic for loop to compute a Riemann sum. * @param f The function that is to be summed. * @param a The left endpoint of the interval over which f is summed. * @param b The right endpoint. * @param n The number of subdivisions of the interval. * @return the value computed for the Riemann sum. */ private static double riemannSumWithForLoop( DoubleUnaryOperator f, double a, double b, int n) { double sum = 0; double dx = (b - a) / n; for (int i = 0; i < n; i++) { sum = sum + f.applyAsDouble(a + i*dx); } return sum * dx; } /** * Use a sequential stream to compute a Riemann sum. */ private static double riemannSumWithStream( DoubleUnaryOperator f, double a, double b, int n) { double dx = (b - a) / n; double sum = IntStream.range(0,n) .mapToDouble( i -> f.applyAsDouble(a + i*dx) ) .sum(); return sum * dx; } /** * Use a parallel stream to compute a Riemann sum. */ private static double riemannSumWithParallelStream( DoubleUnaryOperator f, double a, double b, int n) { double dx = (b - a) / n; double sum = IntStream.range(0,n).parallel() .mapToDouble( i -> f.applyAsDouble(a + i*dx) ) .sum(); return sum * dx; } /** * Compute the same Riemann sum using each the three methods, and * report the time that each method takes. */ private static void timedExperiment( DoubleUnaryOperator f, double a, double b, int n ) { long start, end; double ans; System.out.printf("For n = %,d:%n", n); start = System.nanoTime(); ans = riemannSumWithForLoop(f,a,b,n); end = System.nanoTime(); System.out.printf(" Got %1.15g using a for loop in %,d nanoseconds.%n", ans, end - start); start = System.nanoTime(); ans = riemannSumWithStream(f,a,b,n); end = System.nanoTime(); System.out.printf(" Got %1.15g using a sequential stream in %,d nanoseconds.%n", ans, end - start); start = System.nanoTime(); ans = riemannSumWithParallelStream(f,a,b,n); end = System.nanoTime(); System.out.printf(" Got %1.15g using a parallel stream in %,d nanoseconds.%n", ans, end - start); System.out.println(); } /** * The main program runs experiments to compare the execution time for * the three different methods of computing a Riemann sum. The comparison * is done for n subdivisions of the interval, for several values of n. * The function that is summed is f(x) = sin(x), which takes a relatively * long time to evaluate. */ public static void main(String[] args) { /* First, run each of the three methods to give the Java just-in-time * compiler a chance to optimize the code. Without this "priming" of * the compiler, the times for the first experiment would likely be * longer as the compiler does its work. (Try commenting out these lines * to see the effect.) You might also try running the program with the * just-in-time compiler turned off, using the command * java -Xint RiemannSumStreamExperiment */ riemannSumWithForLoop(Math::sin, 0, Math.PI, 10000); riemannSumWithStream(Math::sin, 0, Math.PI, 10000); riemannSumWithParallelStream(Math::sin, 0, Math.PI, 10000); /* Run the experiment for n from one thousand to 100 million */ System.out.println("Running the experiments for f(x) = sin(x):);"); System.out.println(); for (int n = 1000; n <= 100000000; n = 10*n) { timedExperiment( x -> Math.sin(x), 0, Math.PI, n ); } } } // end RiemannSumStreamExperiment