我正在为根据牛顿定律在空间中移动的一组N个粒子构建(并发)模拟器。 我的想法是将每个粒子建模为一个任务,它与其他粒子(任务)相互作用,以获得它们的位置和质量,以便计算它所受的净力。 每个粒子任务都是
while(true){
force = thisParticle.calculateNetForce(allTheParticles);
thisParticle.waitForAllTheParticlesToCalculateNetForce(); // synchronization
thisParticle.updatePosition(force);
thisParticle.waitForAllTheParticlesToUpdateTheirState(); // synchronization
}
我可以拥有大量粒子(100或更多),因此我无法创建如此多的Java线程(映射到物理线程)。
我的想法是使用Runtime.getRuntime().availableProcessors()+1
个线程来执行许多任务。
但是,我不能使用FixedThreadExecutor,因为粒子任务不会结束。我想使用FixedThreadExecutor,它必须也能够在内部执行某种调度。你知道为此目的吗?
或者,您是否可以通过并发的角度(例如,不同的任务分解)向我建议更好的方法来建模这样的系统?
P.s。:我仅限于“经典”并发机制,不包括演员或类似架构。
答案 0 :(得分:5)
性能的最大杀手可能是您执行的线程安全检查,以确保所有粒子以线程安全的方式进行交互。我建议你每个核心使用一个线程,并尽量减少线程之间的交互。这可以通过将空间划分为线程来完成,例如半X,半Y,半Z将空间划分为8.您可以同时独立地查看每个空间中的所有交互,只需要在粒子从一个空格/线程传递到另一个空间/线程时担心。
答案 1 :(得分:3)
我会假设您将所有粒子存储在一个二维数组中?这将是Fork-Join Framework的一个很好的候选人。
您可以将数组部分的计算分成更小的部分。你继续分裂直到一定的大小。最后你计算并返回。然后,返回的值将与树的另一侧连接并计算。
答案 2 :(得分:2)
我会创建一个具有适当线程数的ExecutorService,而不是每个粒子的一个线程。我会将粒子保存在列表(或其他类型的集合)中。我将为每个粒子的计算和更新步骤创建要执行的单独工作(作为Runnable或Callable)。当你向执行者提交一份工作时,你会得到一个未来。把这些未来放在一个集合中。在您提交了要并行运行的所有工作后,您将遍历您的期货列表并在每个期货上调用get()
以实现同步步骤。
你最终可能会创建一个小的POJO来关联一个粒子和它的计算力(或者隐藏粒子实例中的计算力)。
答案 3 :(得分:1)
为什么不以离散步骤进行计算?
while(true){
for(Particle p : allParticles){
force = p.calculateNetForce(allParticles);
p.setNextPosition(force); //Remembers, but doesn't change the current position
}
for(Particle p : allParticles){
p.nextState(); //Change the position
}
}
首先计算每个粒子的力,但不要改变其当前状态。在为每个粒子计算出它之后,然后根据之前的计算更新其内部状态。通过这种方式,即使单个线程也足够了,当然你可以在多个线程之间拆分计算,但是你需要额外的同步
使用Java 8,您可以利用多核系统,而无需处理线程,同步等。
while(true){
allParticles.parallelStream().forEach(p -> {
double force = p.calculateNetForce(allParticles);
p.setNextPosition(force)
});
allParticles.parallelStream().forEach(p -> p.nextState());
}
答案 4 :(得分:1)
对于每个粒子,你调用calculateNetForce(allTheParticles)
,我认为这使你的计算与O(N ^ 2)(所有粒子数的平方)成正比。这是主要的性能杀手,你最好找到一个复杂度为O(N)的算法,然后才尝试并行化。在我的头顶,我可以建议首先计算所有粒子的总质量和重心。然后,对于每个粒子,计算质量和其余粒子的中心。这可以通过获取总质量和中心,并添加具有负质量而不是当前粒子的“孔”来完成。然后计算粒子与其余粒子之间的力。每个粒子的计算是独立的,可以用其他评论者提出的任何方法进行并行化。
答案 5 :(得分:0)
粒子本身应该是Runnable和Callable,这样可以避免创建大量额外的对象并同步不同的步骤。这是一个SSCCEE:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Particle implements Callable<Void> {
private enum ParticleState {
POSITION_UPDATED, FORCE_CALCULATED
}
private int id;
private int calculatedForce;
private ParticleState particleState = ParticleState.POSITION_UPDATED;
private List<Particle> allTheParticles;
public Particle(int id, List<Particle> allTheParticles) {
this.id = id;
this.allTheParticles = allTheParticles;
}
private void calculateNetForce() {
System.out.println("calculation in " + id);
String someIntenseOperation = "";
for (int i = 0; i < 10000; i++) {
someIntenseOperation += allTheParticles.size();
}
calculatedForce = 0;
particleState = ParticleState.FORCE_CALCULATED;
}
private void updatePosition() {
System.out.println("updating position of " + id);
particleState = ParticleState.POSITION_UPDATED;
}
@Override
public Void call() throws Exception {
switch (particleState) {
case FORCE_CALCULATED:
updatePosition();
break;
case POSITION_UPDATED:
calculateNetForce();
break;
}
return null;
}
public static void main(String[] args) throws InterruptedException {
final ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() + 1);
final List<Particle> allTheParticles = new ArrayList<>();
for (int i = 0; i < 20; i++) {
allTheParticles.add(new Particle(i, allTheParticles));
}
while (true) {
executor.invokeAll(allTheParticles);
executor.invokeAll(allTheParticles);
}
}
}
答案 6 :(得分:0)
因为验证碰撞通常需要n ^ 2次计算,所以划分空间是一个好主意。虽然,它基本上是一个O(n ^ 2)问题。
这个问题声称矩阵逼近(但请查看Parallel computing以了解处理它的最佳想法) 您可以使用此处指出的一些技巧: An efficient way to simulate many particle collisions?
请注意,使用Actor model应该是一个坏主意,因为线程在一定数量后会出现问题。
现在有Java OpenCL lib(例如:Aparapi)和Java 9应该在Sumatra项目中本地带来openCL。因此,您可以使用Fork和Join lib,JVM将使用OpenCL。