并行流处理耗时测量结果异常,如何修正测量方法?
First off, those inconsistent timings are super common when measuring Java code performance—let’s break down why they’re happening and how to fix your method. You mentioned the code is already compiled, but keep in mind Java’s JIT compiler does further runtime optimizations (like translating bytecode to native machine code) that kick in after the first few runs, which contributes to the initial slowdown.
Common Causes of the Timing Anomaly
- JVM Warm-Up Overhead: The first parallel stream run handles all the heavy setup: initializing the shared
ForkJoinPool, JIT-compiling stream operations, and loading necessary classes. By the second run, all that work is already done, so it’s faster. - Single Run Variability: Timing a single execution is never reliable. OS thread scheduling, background garbage collection, or other processes on your machine can skew results drastically.
- Thread Pool Reuse: Parallel streams rely on
ForkJoinPool.commonPool(). The first run spins up pool threads; the second reuses them, avoiding thread creation overhead.
Step-by-Step Fixes for Your Measurement Method
1. Add a JVM Warm-Up Phase
Run your stream operations a few times before taking actual measurements to let the JVM finish optimizing. Here’s how to adjust your code:
static void streamSpeed() { int[] numbers = new int[1000]; for(int i = 0; i < numbers.length; i++) { numbers[i] = i; // Initialize your array properly } // Warm-up: run 5 times to let JVM optimize and initialize resources for (int warmUp = 0; warmUp < 5; warmUp++) { numbers.parallelStream().mapToDouble(Math::sqrt).sum(); } // Now measure the actual runs long start1 = System.nanoTime(); numbers.parallelStream().mapToDouble(Math::sqrt).sum(); long end1 = System.nanoTime(); System.out.println("First parallel stream: " + (end1 - start1) + " ns"); long start2 = System.nanoTime(); numbers.parallelStream().mapToDouble(Math::sqrt).sum(); long end2 = System.nanoTime(); System.out.println("Second parallel stream: " + (end2 - start2) + " ns"); }
2. Average Multiple Runs Instead of Single Executions
Instead of timing one run each, execute each stream operation multiple times and take the average. This smooths out random variability:
static void streamSpeed() { int[] numbers = new int[1000]; for(int i = 0; i < numbers.length; i++) { numbers[i] = i; } // Warm-up phase for (int warmUp = 0; warmUp < 5; warmUp++) { numbers.parallelStream().mapToDouble(Math::sqrt).sum(); } // Measure first stream over 10 runs long total1 = 0; int iterations = 10; for (int i = 0; i < iterations; i++) { long start = System.nanoTime(); numbers.parallelStream().mapToDouble(Math::sqrt).sum(); total1 += System.nanoTime() - start; } System.out.println("First parallel stream average: " + (total1 / iterations) + " ns"); // Measure second stream over 10 runs long total2 = 0; for (int i = 0; i < iterations; i++) { long start = System.nanoTime(); numbers.parallelStream().mapToDouble(Math::sqrt).sum(); total2 += System.nanoTime() - start; } System.out.println("Second parallel stream average: " + (total2 / iterations) + " ns"); }
3. Use JMH for Precise, Repeatable Benchmarks
If you want rock-solid, production-grade results, use the Java Microbenchmark Harness (JMH). It automatically handles warm-up, isolation, and statistical analysis to eliminate common benchmarking pitfalls. A simple JMH benchmark for your case would look like this:
import org.openjdk.jmh.annotations.*; import java.util.concurrent.TimeUnit; @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS) @Warmup(iterations = 5) @Measurement(iterations = 10) @Fork(1) public class StreamBenchmark { private static int[] numbers = new int[1000]; static { for(int i = 0; i < numbers.length; i++) { numbers[i] = i; } } @Benchmark public double parallelStream() { return numbers.parallelStream().mapToDouble(Math::sqrt).sum(); } @Benchmark public double sequentialStream() { return numbers.stream().mapToDouble(Math::sqrt).sum(); } }
JMH will give you reliable averages with error margins, so you can trust the comparison between parallel and sequential streams.
4. Ensure Clean State Between Runs
Make sure your test data is reset or reinitialized if needed (though in your case, the numbers array is static and unchanging, so this is less critical). Avoid side effects in stream operations (like modifying shared variables) that could skew results between runs.
Key Takeaways
- Always warm up the JVM before measuring performance—even compiled code needs runtime optimizations.
- Average multiple runs to account for random system variability.
- For serious benchmarking, use JMH instead of manual timing—it’s built to solve exactly these kinds of measurement issues.
Content of the question originates from Stack Exchange, asked by Pasha

