xNi*_*ogg 6 c# performance xna 2d
我目前正在使用C#/ XNA开发2D游戏.游戏的核心功能是具有截然不同的行为的子弹(这将是一种子弹地狱游戏).更新所有项目符号可能需要相当长的时间,因为它们的行为可能无限复杂.而且所有人都必须进行1次碰撞检查.最初我只是将它们存储在List中并更新并绘制所有这些,从每个帧中删除列表中的非活动项目符号.然而,当屏幕上有8k子弹时,这很快被证明会减慢游戏速度,所以我决定实现多线程并使用LINQ来帮助提高性能.
事情是它仍在减慢约16k子弹.我被告知如果我做得对,我可以获得高达700万的活跃子弹,所以我对16k不满意......
我还能做些什么来提高性能吗?
代码前的附加信息:我的子弹有速度,方向,角速度,加速度,速度限制和行为的字段.提到的唯一特殊事情是行为.它可以随时修改任何项目符号字段或产生更多项目符号甚至将其自身植入其中,因此我很难应用数据驱动的解决方案并只将所有这些字段存储在数组中而不是列出子弹.
internal class BulletManager : GameComponent
{
public static float CurrentDrawDepth = .82f;
private readonly List<Bullet> _bullets = new List<Bullet>();
private readonly int _processorCount;
private int _counter;
private readonly Task[] _tasks;
public BulletManager(Game game)
: base(game)
{
_processorCount = VariableProvider.ProcessorCount;
_tasks = new Task[_processorCount];
}
public void ClearAllBullets()
{
_bullets.Clear();
}
public void AddBullet(Bullet bullet)
{
_bullets.Add(bullet);
}
public override void Update(GameTime gameTime)
{
if (StateManager.GameState != GameStates.Ingame &&
(StateManager.GameState != GameStates.Editor || EngineStates.GameStates != EEngineStates.Running))
return;
var bulletCount = _bullets.Count;
var bulletsToProcess = bulletCount / _processorCount;
//Split up the bullets to update among all available cores using Tasks and a lambda expression
for (var i = 0; i < _processorCount; ++i )
{
var x = i;
_tasks[i] = Task.Factory.StartNew( () =>
{
for(var j = bulletsToProcess * x; j < bulletsToProcess * x + bulletsToProcess; ++j)
{
if (_bullets[j].Active)
_bullets[j].Update();
}
});
}
//Update the remaining bullets (if any)
for (var i = bulletsToProcess * _processorCount; i < bulletCount; ++i)
{
if (_bullets[i].Active)
_bullets[i].Update();
}
//Wait for all tasks to finish
Task.WaitAll(_tasks);
//This is an attempt to reduce the load per frame, originally _bullets.RemoveAll(s => !s.Active) ran every frame.
++_counter;
if (_counter != 300) return;
_counter = 0;
_bullets.RemoveAll(s => !s.Active);
}
public void Draw(SpriteBatch spriteBatch)
{
if (StateManager.GameState != GameStates.Ingame && StateManager.GameState != GameStates.Editor) return;
spriteBatch.DrawString(FontProvider.GetFont("Mono14"), _bullets.Count.ToString(), new Vector2(100, 20),
Color.White);
//Using some LINQ to only draw bullets in the viewport
foreach (var bullet in _bullets.Where(bullet => Camera.ViewPort.Contains(bullet.CircleCollisionCenter.ToPoint())))
{
bullet.Draw(spriteBatch);
CurrentDrawDepth -= .82e-5f;
}
CurrentDrawDepth = .82f;
}
}
Run Code Online (Sandbox Code Playgroud)
哇.你发布的代码有很多错误(也可能是你没有发布的代码).以下是您需要做的事情,大致按重要性/必要性的降序排列:
衡量表现.在最基本的级别是帧速率计数器(或者更好的是帧时间计数器).你想检查一下你做得更好.
在游戏循环期间不要分配内存.检查您是否使用CLR Profiler的最佳方法.虽然您可能没有使用new(分配class类型,structs没问题),但如果LINQ的大部分内容都是在幕后分配内存,那就不会让我感到惊讶.
请注意,ToString将分配内存.StringBuilder如果需要,可以使用无分配方式(使用)来绘制数字.
不要使用LINQ.LINQ简单方便,绝对不是操作集合的最快或最节省内存的方法.
使用数据驱动的方法.数据驱动方法背后的关键思想是保持缓存一致性(更多信息).也就是说:所有Bullet数据都以线性方式存储在内存中.要做到这一点,请确保Bullet是a struct并将它们存储在a中List<Bullet>.这意味着当一个Bullet人加载到CPU缓存中时,它会带来其他内容(内存以大块的形式加载到缓存中),从而减少CPU花费等待内存加载的时间.
要快速删除项目符号,请使用列表中的最后一个项目符号覆盖要删除的项目符号,然后删除最后一个项目.这允许您删除元素而不复制大部分列表.
使用SpriteBatch与性能的头脑.Begin()/End()为子弹做一批单独的精灵(块).使用SpriteSortMode.Deferred- 它是迄今为止最快的模式.进行排序(如你所暗示的那样CurrentDrawDepth)很慢!确保所有项目符号都使用相同的纹理(必要时使用纹理图集).请记住,如果连续的精灵共享一个纹理,那么批处理只会提高性能.(更多信息)
如果你使用得SpriteBatch很好,那么绘制所有精灵可能会更快,然后让GPU剔除它们,如果它们在屏幕外.
(可选)为每个行为维护一个不同的列表.这减少了代码中的分支数量,并可能使代码本身(即:指令,而不是数据)更加缓存一致.与上述要点不同,这只会带来很小的性能提升,因此只有在需要时才能实现.
(注意:除此之外,这些更改很难实现,会使您的代码难以阅读,甚至可能使代码变慢.只有在绝对必要时才实现这些更改并且您正在测量性能.)
(可选)内联您的代码.一旦开始涉及数千个子弹,您可能需要内联代码(删除方法调用)以挤出更多性能.C#编译器没有内联,JIT只做了一点,所以你需要手动内联.方法调用包括可能在向量上使用的+和*运算符之类的内容- 内联这些将提高性能.
(可选)使用自定义着色器.如果您想要比简单使用更多的性能SpriteBatch,请编写一个自定义着色器,它可以获取您的Bullet数据并在GPU上尽可能多地计算.
(可选)使您的数据更小,并且(如果可能)不可变.将初始条件(位置,方向,时间戳)存储在Bullet结构中.然后使用基本运动方程仅在您需要时计算当前位置/速度/等.您通常可以"免费"进行这些计算 - 因为您在等待内存时可能没有未使用的CPU时间.
如果您的数据是不可变的,那么您可以避免每帧都将其传输到GPU上!(如果要添加/删除项目符号,则必须在这些帧上的GPU上更新它).
如果你实现了所有这些项目,我想你可能会在一台好机器上获得多达700万颗子弹.虽然这可能会留下很少的CPU时间留给游戏的其余部分.