You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Java串行代码执行耗时反而比并行代码更短?

问题分析与解决方案

你的并行求和代码没达到预期加速比反而更慢,核心原因集中在这几点:

1. 计算任务太小,并行开销远超收益

你只用了10个元素的数组,串行求和的计算量微乎其微,但并行模式下要创建任务对象、调度线程、执行fork()/join()操作,这些额外开销远大于计算本身,自然比串行慢。

2. 单次测试误差过大

单次计时受JVM即时编译、系统进程调度等因素影响极大,一次结果无法反映真实性能。

3. 缺少JVM预热

刚启动的JVM还没对代码进行即时编译(JIT),第一次运行的速度不能代表稳定状态下的性能。


具体优化方案

方案一:放大计算规模

把数组大小调整到100万甚至1000万,让计算量足以覆盖并行调度的开销。

方案二:多次测试取平均值

重复运行多次计算,跳过第一次的预热结果,取后续运行的平均时间,消除偶然误差。

方案三:改用RecursiveTask规范实现

RecursiveAction是无返回值任务,求和这类有返回值的场景更适合用RecursiveTask,无需手动维护result变量,符合Fork/Join框架设计规范。

方案四:设置并行阈值

当任务拆分到足够小时(比如数组长度小于1000),直接切换为串行计算,避免过度拆分带来的额外开销。


修改后的示例代码

import java.util.Arrays;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;

public class TestProgram {
    public static double addSequential(double[] values) {
        double sum = 0;
        for (double value : values) {
            sum += value;
        }
        return sum;
    }

    public static double addParallel(double[] values) {
        ForkJoinPool pool = ForkJoinPool.commonPool();
        return pool.invoke(new SumArrayTask(0, values.length, values));
    }

    public static void main(String[] args) {
        // 放大数组规模到1000万
        int size = 10_000_000;
        double[] arr = new double[size];
        for (int i = 0; i < size; i++) {
            arr[i] = Math.floor(100 * Math.random());
        }

        // JVM预热:先跑几次计算,触发JIT编译
        for (int i = 0; i < 5; i++) {
            addSequential(arr);
            addParallel(arr);
        }

        // 串行测试:重复5次取平均
        long sequentialTotal = 0;
        for (int i = 0; i < 5; i++) {
            long start = System.nanoTime();
            double seqResult = addSequential(arr);
            sequentialTotal += System.nanoTime() - start;
            if (i == 0) System.out.println("Result (sequential): " + seqResult);
        }
        long sequentialAvg = sequentialTotal / 5;
        System.out.println("Average Sequential Time: " + sequentialAvg + " ns");

        // 并行测试:重复5次取平均
        long parallelTotal = 0;
        for (int i = 0; i < 5; i++) {
            long start = System.nanoTime();
            double parResult = addParallel(arr);
            parallelTotal += System.nanoTime() - start;
            if (i == 0) System.out.println("Result (parallel): " + parResult);
        }
        long parallelAvg = parallelTotal / 5;
        System.out.println("Average Parallel Time: " + parallelAvg + " ns");

        System.out.println("Speedup: " + (double) sequentialAvg / parallelAvg);
    }

    static class SumArrayTask extends RecursiveTask<Double> {
        // 阈值:任务小于该规模时直接串行计算
        private static final int THRESHOLD = 1000;
        private int start;
        private int end;
        private double[] data;

        public SumArrayTask(int startIndex, int endIndex, double[] arr) {
            this.start = startIndex;
            this.end = endIndex;
            this.data = arr;
        }

        @Override
        protected Double compute() {
            int length = end - start;
            if (length <= THRESHOLD) {
                double sum = 0;
                for (int i = start; i < end; i++) {
                    sum += data[i];
                }
                return sum;
            }
            // 拆分任务并行执行
            int mid = start + length / 2;
            SumArrayTask left = new SumArrayTask(start, mid, data);
            SumArrayTask right = new SumArrayTask(mid, end, data);
            left.fork();
            double rightResult = right.compute();
            double leftResult = left.join();
            return leftResult + rightResult;
        }
    }
}

优化效果说明

  • 放大数组规模后,计算量成为性能主导,并行调度的开销占比大幅降低,在你的四核i7设备上,应该能看到接近2-3倍的加速比。
  • 预热和多次取平均消除了JIT编译、系统调度带来的偶然误差,结果更真实可靠。
  • 阈值判断避免了过度拆分任务,进一步减少了并行的额外开销。

内容的提问来源于stack exchange,提问作者noblecaden

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 20:10:28