我正在玩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倍。我做错了什么?
答案 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 线程。
其次,因为其他人也说你的顺序解决方案可能因为你的输入而更快,因为你也可能测量任务创建。
为了更好地衡量你想要的结果我建议你:
创建一个CyclicBarrier,并将barrier.await()作为调用方法的第一个语句。然后在你的main方法中你也调用barrier.await(),并在每个线程到达屏障后立即启动你的基准测试。 这样您就不必测量线程创建并开始执行,尽管这实际上可能与您相关!这取决于你问题的语义。