Java 即使使用threadpool进行多线程处理,许多短期任务也会变慢 背景
我目前有一个线性物理引擎(但这个问题不需要物理引擎知识),我正在对其中的一些部分进行多线程实验,以期提高效率 其中一个部分是宽相位,在这种情况下,这涉及沿所有3个轴移动所有对象,以检查哪些重叠(所有轴上发生的任何重叠都被视为在宽相位中发生碰撞)。除了使用普通对象外,3轴扫描是完全独立的,因此似乎是多线程处理的好地方 为了避免线程间阻塞的可能性,这3个进程中的每一个进程都会在(如果适用的话)进入多线程之前获取它想要使用的所有数据的本地副本 虽然这些扫描是一个重要的瓶颈,但它们的寿命很短,扫描通常持续1-4毫秒。这是一个实时应用程序,代码每秒运行60次,因此总的滴答时间最多为17ms,因此1-4ms对我来说是一个很长的时间。因为这些扫描是短期的,所以我使用了线程池。特别是一个Java 即使使用threadpool进行多线程处理,许多短期任务也会变慢 背景,java,multithreading,performance,threadpool,Java,Multithreading,Performance,Threadpool,我目前有一个线性物理引擎(但这个问题不需要物理引擎知识),我正在对其中的一些部分进行多线程实验,以期提高效率 其中一个部分是宽相位,在这种情况下,这涉及沿所有3个轴移动所有对象,以检查哪些重叠(所有轴上发生的任何重叠都被视为在宽相位中发生碰撞)。除了使用普通对象外,3轴扫描是完全独立的,因此似乎是多线程处理的好地方 为了避免线程间阻塞的可能性,这3个进程中的每一个进程都会在(如果适用的话)进入多线程之前获取它想要使用的所有数据的本地副本 虽然这些扫描是一个重要的瓶颈,但它们的寿命很短,扫描通常持
Executors.newFixedThreadPool(3)
,3个用于3个轴
我的测试计算机是一个双核的超线程,所以最多4个线程应该很舒服。使用Runtime.getRuntime().availableProcessors()检查代码>
问题:
当运行以下测试代码时,多线程版本的速度要慢得多,在这些代码中,使用线程池运行多个短期任务(单线程或多线程);请参阅配置文件数据。即使多线程部件没有共同的对象,情况也是如此。为什么会这样?有没有办法同时运行许多短期(1-4ms)任务
即使使任务大得多,也只会使多线程版本在性能上接近单线程版本,而不会超出我的预期,这让我觉得我做错了什么
测试代码
公共类BroadPhaseAxisSweep实现可调用{
静态最终int XAXIS=0;
静态最终int YAXIS=1;
静态最终整数ZAXIS=2;
int轴;
int[]轴标记;
布尔[]是静态的;
布尔[]加权;
布尔[]是可集合的;
//订单与订单相同
双[]启动;
双[]端;
私有静态ExecutorService扫描池=Executors.newFixedThreadPool(3);
公共宽相轴扫描(int轴,列出所有对象){
//线程将使用的所有数据都在内部缓存,以避免
//任何并发访问问题
这个轴=轴;
//AllObject实际上是未排序的,AxisIndicates保存排序的索引
//在这种情况下,AllObject只是“碰巧”已经被排序了
this.axisIndicies=newint[allObjects.size()];
对于(int i=0;i如果您的大范围扫描只需要几毫秒,那么您最好同步执行所有操作。保留线程池线程所需的工作量(在Windows上)所需时间超过5毫秒。更不用说,您仍然需要在线程之间移动数据,等待上下文切换,然后最终将线程放回找到它们的位置
整个过程可能只会降低性能,特别是因为您正在获取数据的本地副本。如果每次扫描都是自然独立的,并且耗时500毫秒以上,那么您可能会受益于您所实现的某种并发模型
值得注意的一件有趣的事情是,如今的图形处理器配备了专用于物理计算的嵌入式协处理器。它们之所以如此擅长这样做,是因为它们有时有数千个处理器内核,都以相对较低的时钟速率运行。这意味着它们非常适合承担大量任务同时执行小型任务。您可能希望尝试直接与图形处理器接口,以将物理处理卸载到这种环境中,而不是在普通CPU上使用它。我承诺在这里总结所有发现……这相当令人困惑,因为主要原因是分析器减慢了非主处理器的速度线程与主线程不成比例。可能它使用原子计数器来跟踪它的数据,可能它们的开销太高,导致了不合理的结果
手动测量时间可以获得更好的结果,即多线程加速30-40%。这是有意义的,因为数据复制会带来很大的顺序开销
此复制既没有必要也没有用处。它没有必要,因为所有线程只读取共享数据。它没有用处,因为读取共享变量并不比读取自己的副本慢:
- 内核能够非常快地从彼此的缓存中获取数据
- 他们将其副本放入本地L1和L2缓存(协议的“共享”状态)
- 三级缓存是共享的,这意味着不必要的复制数据意味着由于更高的内存占用而导致更多的三级未命中
投机猖獗-。为了测量开销,我会尝试创建一个newFixedThreadPool(1)
。也许还会创建newFixedThreadPool(2)
因为超线程并没有将性能提高一倍。为什么从来没有使用YAXIS和ZAXIS?@Alexei这是我从更大的程序中删除这部分的一部分。在实际版本中使用了它们(我可能应该在问题本身中指出-不在手机上时就可以)@maaartinus我添加了这些测试,毫无疑问,多线程“模式”中的单个线程是最慢的,而2/3线程或多或少是最慢的。如果这有帮助的话,我会多次使用我的线程池(事实上它只创建了一次)@RichardTingle这很好,通常是你做事的方式。每次你需要一个新线程时,它不需要为每个线程分配新的堆栈空间,从而节省了时间。然而,现在的问题不是这样。它是
public class BroadPhaseAxisSweep implements Callable<Set<PotentialCollisionPrecursor>> {
static final int XAXIS=0;
static final int YAXIS=1;
static final int ZAXIS=2;
int axis;
int[] axisIndicies;
boolean[] isStatic;
boolean[] isLightWeight;
boolean[] isCollidable;
//orders the same as axisIndicies
double[] starts;
double[] ends;
private static ExecutorService sweepPool = Executors.newFixedThreadPool(3);
public BroadPhaseAxisSweep(int axis, List<TestObject> allObjects) {
//all data that will be used by the thread is cached internally to avoid
//any concurrent access issues
this.axis = axis;
//allObjects is in reality unsorted, axisIndicies holds sorted indices
//in this case allObjects just "happens" to be already sorted
this.axisIndicies =new int[allObjects.size()];
for(int i=0;i<allObjects.size();i++){
axisIndicies[i]=i;
}
isStatic=new boolean[allObjects.size()];
for(int i=0;i<allObjects.size();i++){
isStatic[i]=allObjects.get(i).isStatic();
}
isLightWeight=new boolean[allObjects.size()];
for(int i=0;i<allObjects.size();i++){
isLightWeight[i]=allObjects.get(i).isLightWeightPhysicsObject();
}
isCollidable=new boolean[allObjects.size()];
for(int i=0;i<allObjects.size();i++){
isCollidable[i]=allObjects.get(i).isCollidable();
}
starts=new double[allObjects.size()];
for(int i=0;i<allObjects.size();i++){
starts[i]=allObjects.get(i).getStartPoint();
}
ends=new double[allObjects.size()];
for(int i=0;i<allObjects.size();i++){
ends[i]=allObjects.get(i).getEndPoint();
}
}
@Override
public Set<PotentialCollisionPrecursor> call() throws Exception {
return axisSweep_simple(axisIndicies);
}
private Set<PotentialCollisionPrecursor> axisSweep_simple(int[] axisIndicies){
Set<PotentialCollisionPrecursor> thisSweep =new HashSet();
for(int i=0;i<starts.length;i++){
if (isCollidable[axisIndicies[i]]){
double activeObjectEnd=ends[i];
//sweep forwards until an objects start is before out end
for(int j=i+1;j<starts.length;j++){
//j<startXsIndicies.length is the bare mininmum contrain, most js wont get that far
if ((isStatic[axisIndicies[i]]&& isStatic[axisIndicies[j]]) || ((isLightWeight[axisIndicies[i]]&& isLightWeight[axisIndicies[j]]))){
//if both objects are static or both are light weight then they cannot by definition collide, we can skip
continue;
}
if (activeObjectEnd>starts[j]){
PotentialCollisionPrecursor potentialCollision=new PotentialCollisionPrecursor(getObjectNumberFromAxisNumber(i),getObjectNumberFromAxisNumber(j));
thisSweep.add(potentialCollision);
}else{
break; //this is as far as this active object goes
}
}
}
}
return thisSweep;
}
private int getObjectNumberFromAxisNumber(int number){
return axisIndicies[number];
}
public static void main(String[] args){
int noOfObjectsUnderTest=250;
List<TestObject> testObjects=new ArrayList<>();
Random rnd=new Random();
double runningStartPosition=0;
for(int i=0;i<noOfObjectsUnderTest;i++){
runningStartPosition+=rnd.nextDouble()*0.01;
testObjects.add(new TestObject(runningStartPosition));
}
while(true){
runSingleTreaded(testObjects);
runMultiThreadedTreaded(testObjects);
}
}
private static void runSingleTreaded(List<TestObject> testObjects) {
try {
//XAXIS used over and over again just for test
Set<PotentialCollisionPrecursor> xSweep=(new BroadPhaseAxisSweep(XAXIS,testObjects)).call();
Set<PotentialCollisionPrecursor> ySweep=(new BroadPhaseAxisSweep(XAXIS,testObjects)).call();
Set<PotentialCollisionPrecursor> zSweep=(new BroadPhaseAxisSweep(XAXIS,testObjects)).call();
System.out.println(xSweep.size()); //just so JIT can't possibly optimise out
System.out.println(ySweep.size()); //just so JIT can't possibly optimise out
System.out.println(zSweep.size()); //just so JIT can't possibly optimise out
} catch (Exception ex) {
//bad practice, example only
Logger.getLogger(BroadPhaseAxisSweep.class.getName()).log(Level.SEVERE, null, ex);
}
}
private static void runMultiThreadedTreaded(List<TestObject> testObjects) {
try {
//XAXIS used over and over again just for test
Future<Set<PotentialCollisionPrecursor>> futureX=sweepPool.submit(new BroadPhaseAxisSweep(XAXIS,testObjects));
Future<Set<PotentialCollisionPrecursor>> futureY=sweepPool.submit(new BroadPhaseAxisSweep(XAXIS,testObjects));
Future<Set<PotentialCollisionPrecursor>> futureZ=sweepPool.submit(new BroadPhaseAxisSweep(XAXIS,testObjects));
Set<PotentialCollisionPrecursor> xSweep=futureX.get();
Set<PotentialCollisionPrecursor> ySweep=futureY.get();
Set<PotentialCollisionPrecursor> zSweep=futureZ.get();
System.out.println(xSweep.size()); //just so JIT can't possibly optimise out
System.out.println(ySweep.size()); //just so JIT can't possibly optimise out
System.out.println(zSweep.size()); //just so JIT can't possibly optimise out
} catch (Exception ex) {
//bad practice, example only
Logger.getLogger(BroadPhaseAxisSweep.class.getName()).log(Level.SEVERE, null, ex);
}
}
public static class TestObject{
final boolean isStatic;
final boolean isLightWeight;
final boolean isCollidable;
final double startPointOnAxis;
final double endPointOnAxis;
public TestObject(double startPointOnAxis) {
Random rnd=new Random();
this.isStatic = rnd.nextBoolean();
this.isLightWeight = rnd.nextBoolean();
this.isCollidable = rnd.nextBoolean();
this.startPointOnAxis = startPointOnAxis;
this.endPointOnAxis =startPointOnAxis+0.2*rnd.nextDouble();
}
public boolean isStatic() {
return isStatic;
}
public boolean isLightWeightPhysicsObject() {
return isLightWeight;
}
public boolean isCollidable() {
return isCollidable;
}
public double getStartPoint() {
return startPointOnAxis;
}
public double getEndPoint() {
return endPointOnAxis;
}
}
}
public class PotentialCollisionPrecursor {
//holds the object numbers of a potential collision, can be converted to a real PotentialCollision using a list of those objects
private final int rigidBodyNumber1;
private final int rigidBodyNumber2;
public PotentialCollisionPrecursor(int rigidBodyNumber1, int rigidBodyNumber2) {
if (rigidBodyNumber1<rigidBodyNumber2){
this.rigidBodyNumber1 = rigidBodyNumber1;
this.rigidBodyNumber2 = rigidBodyNumber2;
}else{
this.rigidBodyNumber1 = rigidBodyNumber2;
this.rigidBodyNumber2 = rigidBodyNumber1;
}
}
public int getRigidBodyNumber1() {
return rigidBodyNumber1;
}
public int getRigidBodyNumber2() {
return rigidBodyNumber2;
}
@Override
public int hashCode() {
int hash = 7;
hash = 67 * hash + this.rigidBodyNumber1;
hash = 67 * hash + this.rigidBodyNumber2;
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final PotentialCollisionPrecursor other = (PotentialCollisionPrecursor) obj;
if (this.rigidBodyNumber1 != other.rigidBodyNumber1) {
return false;
}
if (this.rigidBodyNumber2 != other.rigidBodyNumber2) {
return false;
}
return true;
}
}