我正在开发一个Sprite Kit游戏,我需要做一些多线程来维持健康的fps。
在更新时,我调用一个函数来创建许多UIBezierPaths并使用C ++静态库合并它们。
如果我有超过10个形状,帧速率会急剧下降,所以我决定试试GCD并试着用一个单独的线程解决问题。
我把它放在didMoveToView:
中queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
并且在每个帧上调用的函数中,我称之为:
dispatch_async(queue,^(void){[self heavyCalculationsFunc];});
对于那些了解GCD的人来说,很明显它会在每一帧上创建一个新线程,但对我来说还不清楚。
我的问题是,有没有办法重新使用我想在更新时调用的线程?
提前感谢您的帮助!
答案 0 :(得分:19)
如果你需要在每一帧上完成工作,并且需要在渲染帧之前完成,多线程可能不会帮助你,除非你愿意放弃很多努力。
保持帧速率是关于时间的 - 不是CPU资源,只是停留时间。为了保持60fps的帧速率,你有16.67毫秒来完成你所有的工作。(实际上,不到那个,因为SpriteKit和OpenGL需要一些时间来渲染你的工作结果。)这是一个同步问题 - 你有工作,你有一定的时间来完成这项工作,因此提高绩效的第一步是减少工作量或提高效率。
另一方面,多线程通常用于异步问题 - 您需要做的工作,但它现在不需要立即完成,所以你现在可以继续处理你需要做的其他事情(比如在16毫秒内从你的更新方法返回以保持你的帧率)并稍后检查该工作的结果(比如,在后面的帧上)。这两个定义之间有一点摆动空间:几乎所有现代iOS设备都有多核CPU,所以如果你正确地玩你的卡,你可以通过并行化您的工作量。完成这项工作并做得很好,这不是一件小事 - 它已成为serious research and investment by big game studios多年来的主题。
请参阅SpriteKit编程指南中"How a Scene Processes Frames of Animation"下的图。这是你的16毫秒时钟。浅蓝色区域是Apple的SpriteKit(以及OpenGL和其他系统框架)代码负责的16 ms的片段。其他片是你的。让我们展开该图表以便更好看:
如果你在任何这些切片中做了太多工作,或者使SpriteKit的工作量太大,整个事情就会超过16毫秒,你的帧率会下降。
线程化的机会是在同一时间线内在其他CPU上完成一些工作。如果SpriteKit对动作,物理和约束的处理并不依赖于这些工作,那么你可以与这些工作同时进行:
或者,如果你的工作需要在SpriteKit运行动作之前发生。物理,但你需要在update
方法中完成其他工作,你可以在完成剩余的update
工作时将一些工作发送到另一个线程,然后检查结果在update
方法中:
那么如何完成这些事情呢?这是使用调度组的一种方法,并假设动作/物理/约束不依赖于您的背景工作 - 它完全偏离我的头脑,所以它可能不是最好的。 :)
// in setup
dispatch_queue_t workQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t resultCatchingGroup = dispatch_group_create();
id stuffThatGetsMadeInTheBackground;
- (void)update:(NSTimeInterval)currentTime {
dispatch_group_async(group, queue, ^{
// Do the background work
stuffThatGetsMadeInTheBackground = // ...
});
// Do anything else you need to before actions/physics/constraints
}
- (void)didFinishUpdate {
// wait for results from the background work
dispatch_group_wait(resultCatchingGroup, DISPATCH_TIME_FOREVER);
// use those results
[self doSomethingWith:stuffThatGetsMadeInTheBackground];
}
当然,正如其名称所示,dispatch_group_wait
会阻止执行,等待后台工作完成,因此您仍然有16ms的时间限制。如果前台工作(你的update
的其余部分,加上SpriteKit的动作/物理/约束工作以及你完成的任何其他工作都会在你的后台工作完成之前完成),你会等待它。如果后台工作加上SpriteKit的渲染工作(以及在产生后台工作之前你在update
中做的任何事情)花费的时间超过16毫秒,你仍然会丢帧。因此,解决这个问题的方法是充分了解您的工作量,以便安排好。
答案 1 :(得分:3)
考虑一种稍微不同的方法。创建并维护自己的队列,而不是获取系统队列。
a)调用dispatch_queue_create创建一个新队列,将该队列保存在对象中。在该队列上使用dispatch_async来运行您的作业。如果必须在下一帧之前完成,则可能需要进行同步等。
b)如果您有多个作业,请考虑并发队列而不是串行队列,这可能会也可能不会使事情变得更快'取决于你的依赖。
使用GCD,您应该不考虑线程,如果创建/重用新线程等等。只需考虑队列,以及您正在推动它们的内容。阅读Apple的并发编程指南和gcd参考也将有所帮助。