我正在为N-Body问题实现Barnes-Hut算法的多线程解决方案。
Main class执行以下操作
public void runSimulation() {
for(int i = 0; i < numWorkers; i++) {
new Thread(new Worker(i, this, gnumBodies, numSteps)).start();
}
try {
startBarrier.await();
stopBarrier.await();
} catch (Exception e) {e.printStackTrace();}
}
bh.stop-和bh.startBarrier是CyclicBarriers,将start-和stopTime设置为System.nanoTime();到达时(屏障行动)。
工人运行方法:
public void run() {
try {
bh.startBarrier.await();
for(int j = 0; j < numSteps; j++) {
for(int i = wid; i < gnumBodies; i += bh.numWorkers) {
bh.addForce(i);
bh.moveBody(i);
}
bh.barrier.await();
}
bh.stopBarrier.await();
} catch (Exception e) {e.printStackTrace();}
}
addForce(i)遍历树并进行一些计算。它不会影响任何共享变量,因此不使用任何同步。 O(NlogN)。
moveBody(i)对一个元素进行计算,不使用同步。 O(N)。
当达到bh.barrier时,会建立一个包含所有实体的树(屏障动作)。
现在问题。运行时随着使用的线程数线性增加。 gnumBodies的运行时间= 240,numSteps = 85000和四个核心:
为什么运行时不会随着使用的线程数而减少?
编辑:添加硬件信息
答案 0 :(得分:1)
你在运行什么硬件?运行多个线程有其开销,因此在将任务拆分为小的子任务时可能不值得。
另外,尝试使用ExecutorService而不是线程。这样您就可以使用线程池而不是为每个任务创建实际线程。拥有更多可以处理硬件的线程是没有用的。
在我看来,每个线程都会做同样的工作。情况可能如此吗?在创建工作者时,除了i之外,每次都使用相同的参数。
答案 1 :(得分:0)
多线程不会提高执行速度,除非您还有多个CPU核心。
线程进行数学计算并可以全速运行
如果您只有一个CPU核心,如果您在一个线程或多个线程中运行计算,则它们都是相同的。在多个线程中运行不会带来任何性能优势,但会带来线程切换的开销,因此实际上总体性能会稍差一些。
如果您有多个可用的CPU核心,则线程可以物理并行运行,最多可达核心数。这意味着4-8个线程可以在当今的桌面硬件上运行良好。
线程等待IO并被暂停
如果您不进行数学计算,但做一些涉及网络,文件或数据库等慢速I / O的操作,则线程是有意义的。当一个线程等待IO时,不是占用程序的运行,而是另一个线程可以使用相同的CPU核心。这就是为什么Web服务器和数据库解决方案可以使用比CPU核心更多的线程的原因。
避免不必要的同步
然而,您的测量显示同步错误。 我想你应该从线程代码中删除所有xxbarrier.await()。 我不确定xxBarriers与系统纳米级的目标是什么,但任何不必要的同步化都很容易导致性能下降。您没有计算,而是等待xxxBarriers。
答案 2 :(得分:0)
您的员工独立完成同样的工作numWorker
次。
唯一的共享对象是您的CyclicBarrier。 await()
等待所有奇偶校验在此障碍上等待。随着工人数量的增加,它会花费更多时间在await()
答案 3 :(得分:0)
如果您有多个内核或者超线程可用,那么运行多个线程将获得底层硬件的好处。
如果只存在一个核心,如果您的应用程序涉及至少一个非CPU密集型工作(如与人的交互),则多线程可以提供“感知”的好处。与现代CPU相比,人类非常缓慢。因此,如果您的应用程序需要从人类获取多个输入并处理它们,那么在两个线程中分开输入和计算是有意义的。到人类将提供输入时,部分计算可以在另一个线程中完成。从而整体改善了时间。
如果应用程序必须进行计算并且不存在硬件中的多线程支持,则最好使用单线程。您的“计算”已经在管道中背靠背排列,CPU已经以(几乎)最大速度运行。多线程需要上下文切换时间,这将增加进行计算所需的时间。
答案 4 :(得分:0)
当我运行具有更多主体的应用程序更少的步骤时,应用程序按预期缩放。所以问题可能就是屏障的开销!