Java并行执行比后续

时间:2016-01-07 12:01:57

标签: java parallel-processing

我正在玩Java中的并行执行。昨天我试着测量执行时间并得到一些不清楚的结果。

任务:使用并行模式和顺序求和数组。这是我的并行代码:

public static int sumArrayParallel(int[] numbers) throws ExecutionException, InterruptedException {
    int cpus = Runtime.getRuntime().availableProcessors();
    ExecutorService service = Executors.newFixedThreadPool(cpus);
    List<FutureTask<Integer>> tasks = new ArrayList<>();
    int blockSize = (numbers.length + cpus - 1) / cpus;

    for (int i = 0; i < numbers.length; i++) {
        final int start = blockSize * i;
        final int end = Math.min(blockSize * ( i + 1 ), numbers.length);
        FutureTask<Integer> futureTask = new FutureTask<Integer>(new Callable<Integer>() {
            public Integer call() {
                int sub = 0;
                for (int j = start; j < end; j++)
                    sub += numbers[j];
                return sub;
            }
        });
        tasks.add(futureTask);
        service.execute(futureTask);
    }
    int sum = 0;
    for(Future<Integer> task: tasks)
        sum += task.get();
    service.shutdown();        
    return  sum;
}

顺序很简单:

  public static int sumArraySequential(int[] arr) {
    int sum = 0;
    for( int num : arr ) {
        sum += num;
    }
    return  sum;
};

因此,顺序功能比并行功能快2-4倍。我做错了什么?

4 个答案:

答案 0 :(得分:1)

总结,处理明智,是一个真正简单的任务。增加是一个 CPU周期。

从内存中获取数据是一项非常昂贵的任务。根据您的阵列大小,它可能存在于主存储器中,而不是任何L1,L2,L3缓存中。从主存储器中获取数据需要数百个的CPU周期。

现在,当您按顺序进行求和时,在单个线程上,CPU假定您需要从您正在处理的部件中获得更多内存,并在L1 / L2 / L3高速缓存中预先加载它。这种优化基本上完全消除了数百个CPU周期&#34;从主内存中获取数据,因为数据在您想要求它的时候已经在缓存中了。

当您现在尝试并行化任务时,您需要将阵列拆分为多个块。优化器不知道要加载到缓存中的哪些部分,因为它们可以不按顺序执行。对于并行任务,您可能已经没有缓存中的数据,导致必须等待数百个CPU周期才能从主存中获取数据。

所以最后,你的任务不受你的CPU可以做的处理的数量(通过并行化增加)的限制,但是从内存中获取数据的数量和速度(这是在单个顺序程序中更容易优化)。这可能解释了你的意想不到的&#34;结果

此外,根据您的输入大小,线程的初始化比处理花费更多的时间,但我只能假设您使用大型数组,因此并不重要。

答案 1 :(得分:1)

在顺序版本中,您只使用原语,这本身就很快。

在并行或并发版本中,您创建了许多对象,这些对象在创建和使用中都会产生开销。

您没有说明您测试过的数组大小。我猜想对于较大的numbers.length值,性能会相对更好。

答案 2 :(得分:1)

您的代码不对。

您正在创建N 元素任务,而您应创建M 任务。 : - )

修复主循环

    int sum = 0;
    for(Future<Integer> task: tasks) {
        sum += task.get();
        System.out.println(sum);
    }

迭代块,而不是元素。

PS。如果你稍微改变你的代码,你就会清楚地看到发生了什么

es = Elasticsearch()
s = Search(es)   
s = s.filter("term", status="Free")
s.to_dict()
{'query': {'filtered': {'filter': {'term': {'status': 'Free'}}, 'query': {'match_all': {}}}}}

答案 3 :(得分:0)

首先,Leo说你必须修复你的循环,这样就不会创建 numbers.length 线程。

其次,因为其他人也说你的顺序解决方案可能因为你的输入而更快,因为你也可能测量任务创建。

为了更好地衡量你想要的结果我建议你:

  • 确保您使用的计算机有足够的内核来实际执行更快的并行运行
  • 使用大型输入数组(至少数百万个元素)。
  • 拿一个https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CyclicBarrier.html这是一个为您提供等待线程的选项,以便&#34;见面&#34;甚至&#34;会合&#34;稍后的。假设您同意在9:00与5位朋友见面。然后你等待你的朋友,然后你可能会在9:00或者9:05聚集,无论如何你等待。然后你可能会同意分开的事情,并在11:00再次见面,依此类推。这对你很有帮助,因为你可以设置障碍:

创建一个CyclicBarrier,并将barrier.await()作为调用方法的第一个语句。然后在你的main方法中你也调用barrier.await(),并在每个线程到达屏障后立即启动你的基准测试。 这样您就不必测量线程创建并开始执行,尽管这实际上可能与您相关!这取决于你问题的语义。