Sprite Kit 中可重用的多线程实现
reusable multithread implementation in Sprite Kit
我正在开发 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 的人来说,它在每一帧上创建一个新线程可能是显而易见的,但对我来说还不清楚。
我的问题是,有没有什么方法可以重新使用我想在更新时调用的线程?
提前感谢您的帮助!
考虑一种稍微不同的方法。创建和维护您自己的队列,而不是获取系统队列。
a) 调用 dispatch_queue_create 创建一个新队列,将此队列保存在您的对象中。在该队列上使用 dispatch_async 来 运行 你的工作。如果必须在下一帧之前完成,您可能需要同步,等等。
b) 如果您有多个作业,请考虑使用并发队列而不是串行队列,这可能会或可能不会 'faster' 取决于您的依赖关系。
对于 GCD,您不应该考虑线程,如果新线程是 created/reused 等。只需考虑队列,以及您向其推送的内容。阅读 Apple 的 Concurrency Programming Guide 和 gcd 参考资料也有望有所帮助。
如果您需要在每一帧上完成工作,并且需要在渲染帧之前完成这些工作,那么多线程可能帮不了您,除非您愿意为此付出很多努力它。
保持帧速率的关键在于时间——而不是 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 毫秒的片段。其他切片是你的。让我们展开该图以便更好地查看:
如果您在任何这些切片中做太多工作,或者使 SpriteKit 的工作量太大,整个过程将超过 16 毫秒并且您的帧率会下降。
线程的机会是在同一时间轴上完成另一个 CPU 的一些工作。如果 SpriteKit 对动作、物理和约束的处理不依赖于这项工作,您可以与这些事情并行进行:
或者,如果您的工作需要在 SpriteKit 运行动作和物理之前进行,但是您还有其他工作需要在 update
方法中完成,您可以将一些工作发送到另一个线程,同时完成剩下的 update
工作,然后在 update
方法中检查结果:
那么如何完成这些事情呢?这是一种使用调度组的方法,并假设 actions/physics/constraints 不依赖于您的后台工作——这完全不在我的脑海中,所以它可能不是最好的。 :)
// 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
将如其名称所示阻止执行以等待后台工作完成,因此您仍然有 16 毫秒的时间限制。如果前台工作(你的其余 update
,加上 SpriteKit 的 actions/physics/constraints 工作以及你为响应这些事情而完成的任何其他工作)在你的后台工作之前完成,你将等待它。如果后台工作加上 SpriteKit 的渲染工作(加上您在生成后台工作之前在 update
中所做的任何事情)花费的时间超过 16 毫秒,您仍然会丢帧。所以这样做的诀窍是足够详细地了解您的工作量,以便很好地安排它。
我正在开发 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 的人来说,它在每一帧上创建一个新线程可能是显而易见的,但对我来说还不清楚。
我的问题是,有没有什么方法可以重新使用我想在更新时调用的线程?
提前感谢您的帮助!
考虑一种稍微不同的方法。创建和维护您自己的队列,而不是获取系统队列。
a) 调用 dispatch_queue_create 创建一个新队列,将此队列保存在您的对象中。在该队列上使用 dispatch_async 来 运行 你的工作。如果必须在下一帧之前完成,您可能需要同步,等等。
b) 如果您有多个作业,请考虑使用并发队列而不是串行队列,这可能会或可能不会 'faster' 取决于您的依赖关系。
对于 GCD,您不应该考虑线程,如果新线程是 created/reused 等。只需考虑队列,以及您向其推送的内容。阅读 Apple 的 Concurrency Programming Guide 和 gcd 参考资料也有望有所帮助。
如果您需要在每一帧上完成工作,并且需要在渲染帧之前完成这些工作,那么多线程可能帮不了您,除非您愿意为此付出很多努力它。
保持帧速率的关键在于时间——而不是 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 毫秒的片段。其他切片是你的。让我们展开该图以便更好地查看:
如果您在任何这些切片中做太多工作,或者使 SpriteKit 的工作量太大,整个过程将超过 16 毫秒并且您的帧率会下降。
线程的机会是在同一时间轴上完成另一个 CPU 的一些工作。如果 SpriteKit 对动作、物理和约束的处理不依赖于这项工作,您可以与这些事情并行进行:
或者,如果您的工作需要在 SpriteKit 运行动作和物理之前进行,但是您还有其他工作需要在 update
方法中完成,您可以将一些工作发送到另一个线程,同时完成剩下的 update
工作,然后在 update
方法中检查结果:
那么如何完成这些事情呢?这是一种使用调度组的方法,并假设 actions/physics/constraints 不依赖于您的后台工作——这完全不在我的脑海中,所以它可能不是最好的。 :)
// 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
将如其名称所示阻止执行以等待后台工作完成,因此您仍然有 16 毫秒的时间限制。如果前台工作(你的其余 update
,加上 SpriteKit 的 actions/physics/constraints 工作以及你为响应这些事情而完成的任何其他工作)在你的后台工作之前完成,你将等待它。如果后台工作加上 SpriteKit 的渲染工作(加上您在生成后台工作之前在 update
中所做的任何事情)花费的时间超过 16 毫秒,您仍然会丢帧。所以这样做的诀窍是足够详细地了解您的工作量,以便很好地安排它。