波西的小球——优化日志

之前由于工作的原因,要写一篇在Windows Store下面如何做多线程开发的文章。老大让我用一个例程贯穿整个文章,并且介绍多线程开发的几个知识点。于是我就用两个小球在水平方向上碰撞的作为例程写了一个系列的文章。

其原理就是利用多线程来移动小球,并且通过一些简单的线程同步的方式,来控制小球的运动。

有兴趣的同学请参考。

Windows store app 多线程编程()

http://social.msdn.microsoft.com/Forums/zh-CN/winstoreappzhcn/thread/b5be6a11-23e4-4c48-9235-1d812ac32816

Windows store app 多线程编程()

http://social.msdn.microsoft.com/Forums/zh-CN/winstoreappzhcn/thread/71c258c2-6f21-40d1-aecb-aa70d977ca99

Windows store app 多线程编程()

http://social.msdn.microsoft.com/Forums/zh-CN/winstoreappzhcn/thread/9ef40747-426c-45b2-8afd-83cd8f380760

Windows store app 多线程编程()

http://social.msdn.microsoft.com/Forums/zh-CN/winstoreappzhcn/thread/305edc10-37ec-4f9c-a56b-45d607aa3e9f

 

之后我就想把这个程序写的复杂一点,毕竟用1个线程控制2个小球,还在水平方向上运动确实弱了一些。


 

所以下一步我就想让更多的小球在二维平面上运动。

如果是更多的小球一起运动,就不能单独用一个线程控制一个小球运动了。所以就要对小球进行封装,加入速度,位置,以及对应的XAMLelement

然后用一个线程去移动每个小球,然后再起一个线程去做碰撞测试。

我用的碰撞测试的算法是这篇文章里面的。

http://www.cnblogs.com/yjmyzz/archive/2010/04/22/1717896.html

这个算法是通过旋转坐标系,让两个小球在切线的法线方向上正碰,再通过动量守恒公式算的。


然后这个版本的效果不是很理想,48个小球,CPU 60% 碰撞测试丢失的情况很多。如果把小球加到168个,CPU就占用了90%

 

 

后来,我想想,估计是XAML的绘图性能不足所致,于是我想用在D2D上面画,然后在通过SurfaceImageSource做成imageXAML里面显示。具体的例程可以参考

http://code.msdn.microsoft.com/windowsapps/XAML-SurfaceImageSource-58f7e4d5

这个例程拿过来可以直接用,我需要的就是把画正方形的变成画小球的。

然后我美化了一下小球,本来想给小球加纹理的,可惜不太会做,后来用放射渐变画刷把光照和纹理一起给做了。

 

可惜效果并不是很理想,48个小球CPU达到70%,效果还不如XAML。可能是因为我打开的方式不对。

 

 

我开始向DX的一个QQ群求助,大家都表示很诧异,首先,在另一个线程中做碰撞测试就很惊讶,然后在对于48个小球CPU占用率这么高,还是用D2D表示惊奇。在轮番被鄙视的过程中,我听到了一个叫四叉树碰撞检测算法。

后来我CPU占用率高的问题,是因为我在画小球的时候创建画刷,在Windows Runtime中,虽然有WRL帮我们释放资源,但是我每次创建,且不显示地删除画刷,实在是太坑了,而且还是放射渐变画刷。

之后我在画的时候,只修改几个画刷的位置,而不是去创建画刷,48个小球CPU的利用率降到了20%,也就是Windows Store正常的水平。

 

但是如果小球数量是100CPU还是60%

 

 

这个时候,就要考虑更新碰撞检测算法了。之前用的碰撞检测算法,就是在两个for循环里面调用碰撞检测函数。于是我就用上面所说的四叉树碰撞检测算法。

算法是通过这个文章改写的

http://www.9ria.com/news/2012/0912/25043.html

实现算法之后,我再进行48个小球碰撞测试,结果CPU没有明显降低,100也没有明显降低,200个,程序崩溃,我觉得是我打开的方式不对。

后来我通过统计函数执行时间的方式发现这个算法有一些可以改进的地方,比如clear函数

public void clear() {
   objects.clear();

   for (int i = 0; i < nodes.length; i++) {
     if (nodes[i] != null) {
       nodes[i].clear();
       nodes[i] = null;
     }
   }
}

clear 的函数是每次都要调的,所以nodes[i] = null;应该删掉,因为每次clear之后,都要再重新分区,重新填入Object,填入Object必须要做,但是分区就不是每次都要做了。因为区域的边界每次都是固定的,每次新建分区,还要新建一个list。

比如,最后的比较算法,文章中是这样说的

所有的对象都插入后,就可以遍历每个对象,得到一个可能会发生碰撞对象的list,然后你就可以在list里的每一个对象间用任何一种碰撞检测的算法检查碰撞,和初始化对象。

List returnObjects = new ArrayList();
for (int i = 0; i < allObjects.size(); i++) {
  returnObjects.clear();
  quad.retrieve(returnObjects, objects.get(i));

  for (int x = 0; x < returnObjects.size(); x++) {
    // Run collision detection algorithm between objects
  }
}

但是,我发现,这个不需要在list对象之间再进行碰撞检测了,最里面的for循环只需要检测i和returnObjects[x]是否发生碰撞就好了。如果在对list里面的对象再两两比较的话,会增加没必要的比较次数。

 

调整好这些之后,我再进行48个小球碰撞测试,结果CPU没有明显降低,100也没有明显降低,200个,程序崩溃。

 

 

然后我在群里说,我实现了四叉树的算法,可是没有改进,DX群里的大牛已经对我菜的失去信心了。

 

 

后来发现,这个四叉树碰撞检测算法,并不是线程安全的。我却在多线程的环境里调用这个算法,那么可能的一种情况就是上个线程没有执行完,下个线程要重建这个四叉树的时候,抛出异常。对于这个问题,我通过加临界区来解决的。

我的临界区是这样加的 

 EnterCriticalSection(&cs);

HittestQuadtree();     

LeaveCriticalSection(&cs);

 

 

解决了抛出异常的问题,用四叉树碰撞检测算法,在200个小球上从CPU90%降到了70%。但是出现了一些新的问题,碰撞丢失和碰撞粘连。这个问题是在两个for循环的碰撞检测中很少出现的。四叉树算法的效果还是不如原始 算法。 

 

 

我再检查了一下碰撞检测的算法,这个碰撞检测算法不仅仅是发现碰撞之后改变小球的方向,还需要分离已经重叠的小球。因为不是实时检测,总会有小球在检测的过程中重叠在一起,这个时候就要去分开,在多线程的环境中,这个问题尤其明显。

上面的算法处理分离的代码是这样的

          // 更新位置

                var absV:Number=Math.abs(vel0.x)+Math.abs(vel1.x);

                var overlap:Number = (ball0.radius + ball1.radius) - Math.abs(pos0.x - pos1.x);

                pos0.x += vel0.x/absV*overlap;

                pos1.x += vel1.x/absV*overlap;

这里有几个问题:

1.       数据精度,如果两个小球都是y方向有速度,而x方向没有速度。absV这个值就是两个小球x方向速度绝对值的和。那这个值可能接近0,但又不是0,这个是因为在用反三角函数求角度,在用这个角度求sin值的时候,sin的函数返回一个高精度接且近于0的值导致的。那么在pos0.x += vel0.x/absV*overlap;的时候,就要除以这个接近0的数,会导致x无限大。这个问题的现象就是两个小球并在一起,以非常快的速度一起运动。后来,我通过降低数据的精度,然后判断除0,来解决这个问题。

2.       速度的方向和X的大小,现在有这样的一种情况,pos0.x<pos1.xvel0.x>0,vel1.x<0,那么按照上面的算法运算之后,小球是相互靠近的,而不是相互分离的。如果pos0.x<pos1.xvel0.x>0,vel1.x>0,那么在速度相同的情况下,小球依然不会分离。

 

为什么在两个for循环的碰撞检测中这种现象不明显,而在四叉树碰撞检测算法特别显著呢?是因为四叉树算法降低了比较的次数,原来的算法这样的错误发生,在下一次循环中就有机会弥补,四叉树算法比较的次数减少,这个机会出现的概率就降低了。

所以四叉树碰撞检测算法在提升效率的同时,需要可靠性更高的碰撞检测算法来达到更好的效果。

 

经过一系列的改进之后,200个小球CPU降到70% 

 

 

再进一步的优化,

·         我把临界区的区域变得更小了。一方面可以保证四叉树算法的线程安全,同时也降低了CPU

·         之前我是在ThreadPoolTimer里面创建两个WorkItem,但这样做的效率不如两个ThreadPoolTimer

·         DX可以降低DPI来降低CPU使用率。

·         另外,增加ThreadPoolTimertimespan,也可以降低CPU,但是这样做的话小球的运动速度就降低,当然,我们也可以增加小球的运动速度,不过加速度之后,会有明显的跳帧。

 

最后200个小球CPU占用率50% 

 

 

………………
………………
………………
………………

 

………………
………………
………………
………………
 

 

最后,

写这篇文章的目的,还是想请更多的高手,一起深入探讨一下这些代码。

我看过网上JS的碰撞检测代码500CPU还不到20%

所以我相信还会有更多改进的地方。

最后由于代码太长,我打包上传,这是用C++/CX写的,大家请自行修改成其他语言的代码。 

https://skydrive.live.com/redir?resid=6C98BB8F525FA790!176 

posted @ 2012-10-17 23:05  Jesse Jiang  阅读(1471)  评论(3编辑  收藏  举报