Sprite Kit中可重用的多线程实现

时间:2022-09-29 07:40:53

I am working on a Sprite Kit game and I need to do some multithreading to maintain the healthy fps.

我正在开发一个Sprite Kit游戏,我需要做一些多线程来维持健康的fps。

On update I call a function to create a lot of UIBezierPaths and merge them using a C++ static library.

在更新时,我调用一个函数来创建许多UIBezierPaths并使用C ++静态库合并它们。

If I have more than 10 shapes the frame rate drops dramatically so I decided I give GCD a try and try to solve the issue with a separate thread.

如果我有超过10个形状,帧速率会急剧下降,所以我决定尝试GCD并试着用一个单独的线程解决问题。

I put this in didMoveToView:

我把它放在didMoveToView中:

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

and in the function that is being called on every frame, I call this:

在每个帧上调用的函数中,我称之为:

dispatch_async(queue,^(void){[self heavyCalculationsFunc];});

For somebody who knows GCD well it might be obvious it creates a new thread on every frame, but for me it wasn't clear yet.

对于那些了解GCD的人来说,很明显它会在每一帧上创建一个新线程,但对我来说还不清楚。

My question is, is there any way to re-use a thread that I want to call on update?

我的问题是,有没有办法重新使用我想要更新的线程?

Thanks for your help in advance!

感谢您的帮助!

2 个解决方案

#1


18  

If you have work that you need to do on every frame, and that needs to get done before the frame is rendered, multithreading probably won't help you, unless you're willing to put a lot of effort into it.

如果你需要在每一帧上完成工作,并且需要在渲染帧之前完成,多线程可能无法帮助你,除非你愿意付出很多努力。

Maintaining a frame rate is all about time — not CPU resources, just wall time. To keep a 60fps framerate, you have 16.67 ms to do all your work in. (Actually, less than that, because SpriteKit and OpenGL need some of that time to render the results of your work.) This is a synchronous problem — you have work, you have a specific amount of time to do it in, so the first step to improving performance is to do less work or do it more efficiently.

保持帧速率几乎是时间 - 而不是CPU资源,只是时间。为了保持60fps帧速率,你有16.67毫秒来完成所有工作。(实际上,不到那个,因为SpriteKit和OpenGL需要一些时间来渲染你的工作结果。)这是一个同步问题 - 你有工作,你有一定的时间来完成这项工作,因此提高绩效的第一步是减少工作量或提高效率。

Multithreading, on the other hand, is generally for asynchronous problems — there's work you need to do, but it doesn't need to get done right now, so you can get on with the other things you need to do right now (like returning from your update method within 16 ms to keep your framerate up) and check back for the results of that work later (say, on a later frame).

另一方面,多线程通常用于异步问题 - 你需要做的工作,但它现在不需要完成,所以你可以继续你现在需要做的其他事情(比如返回)从你的更新方法在16毫秒内保持你的帧率)并检查以后的工作结果(比如,在后面的帧)。


There is a little bit of wiggle room between these two definitions, though: just about all modern iOS devices have multicore CPUs, so if you play your cards right you can fit a little bit of asynchronicity into your synchronous problem by parallelizing your workload. Getting this done, and doing it well, is no small feat — it's been the subject of serious research and investment by big game studios for years.

但是,这两个定义之间存在一些摆动空间:几乎所有现代iOS设备都有多核CPU,因此如果你正确地使用你的卡,你可以通过并行化工作负载在同步问题中适应一点异步性。完成这项工作并做得很好,这不是一件容易的事 - 多年来它一直是大型游戏工作室认真研究和投资的主题。

Take a look at the figure under "How a Scene Processes Frames of Animation" in the SpriteKit Programming Guide. That's your 16 ms clock. The light blue regions are slices of that 16 ms that Apple's SpriteKit (and OpenGL, and other system frameworks) code is responsible for. The other slices are yours. Let's unroll that diagram for a better look:

请参阅SpriteKit编程指南中“场景如何处理动画帧”下的图。那是你的16毫秒时钟。浅蓝色区域是Apple的SpriteKit(以及OpenGL和其他系统框架)代码负责的16 ms的片段。其他片是你的。让我们展开该图表以获得更好的外观:

Sprite Kit中可重用的多线程实现

If you do too much work in any of those slices, or make SpriteKit's workload too large, the whole thing gets bigger than 16 ms and your framerate drops.

如果你在任何这些切片中做了太多工作,或者使SpriteKit的工作量太大,整个事情就会超过16毫秒,你的帧率会下降。

The opportunity for threading is to get some work done on the other CPU during that same timeline. If SpriteKit's handling of actions, physics, and constraints doesn't depend on that work, you can do it in parallel with those things:

线程化的机会是在同一时间线内在另一个CPU上完成一些工作。如果SpriteKit对动作,物理和约束的处理不依赖于那些工作,那么你可以与这些工作并行执行:

Sprite Kit中可重用的多线程实现

Or, if your work needs to happen before SpriteKit runs actions & physics, but you have other work you need to do in the update method, you can send some of the work off to another thread while doing the rest of your update work, then check for results while still in your update method:

或者,如果你的工作需要在SpriteKit运行动作和物理之前发生,但你需要在更新方法中做其他工作,你可以在完成其余的更新工作时将一些工作发送到另一个线程,然后仍在您的更新方法中检查结果:

Sprite Kit中可重用的多线程实现


So how to accomplish these things? Here's one approach using dispatch groups and the assumption that actions/physics/constraints don't depend on your background work — it's totally off the top of my head, so it may not be the best. :)

那么如何完成这些事情呢?这是使用调度组的一种方法,并假设动作/物理/约束不依赖于您的后台工作 - 它完全偏离我的头脑,所以它可能不是最好的。 :)

// 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];
}

Of course, dispatch_group_wait will, as its name suggests, block execution to wait until your background work is done, so you still have that 16ms time constraint. If the foreground work (the rest of your update, plus SpriteKit's actions/physics/constraints work and any of your other work that gets done in response to those things) gets done before your background work does, you'll be waiting for it. And if the background work plus SpriteKit's rendering work (plus whatever you do in update before spawning the background work) takes longer than 16 ms, you'll still drop frames. So the trick to this is knowing your workload in enough detail to schedule it well.

当然,dispatch_group_wait将顾名思义阻止执行,直到你的后台工作完成,所以你仍然有16ms的时间限制。如果前台工作(更新的其余部分,加上SpriteKit的动作/物理/约束工作以及为响应这些事情而完成的任何其他工作)在后台工作之前完成,那么您将等待它。如果后台工作加上SpriteKit的渲染工作(加上你在生成后台工作之前在更新中做的任何事情)花费的时间超过16毫秒,你仍然会丢帧。因此,解决这个问题的方法是充分了解您的工作量以便安排好。

#2


2  

Consider a slightly different approach. Create and maintain your own queue rather than getting a system queue.

考虑一种稍微不同的方法。创建和维护自己的队列而不是获取系统队列。

a) call dispatch_queue_create to make a new queue, save this queue in your object. Use dispatch_async on that queue to run your job. You may need to synchronize if that has to complete before the next frame, etc.

a)调用dispatch_queue_create创建一个新队列,将该队列保存在对象中。在该队列上使用dispatch_async来运行您的作业。如果必须在下一帧之前完成,则可能需要同步等。

b) If you have multiple jobs, consider a concurrent queue instead of a serial queue, which may or may not make things 'faster' depending on your dependencies.

b)如果您有多个作业,请考虑并发队列而不是串行队列,这可能会也可能不会使事情“更快”,具体取决于您的依赖关系。

With GCD you're supposed to not think about threads, if new threads are created/reused etc. Just think about the queues, and what you're pushing on to them. Reading Apple's Concurrency Programming Guide and reference on gcd will also hopefully help.

使用GCD,你应该不考虑线程,如果新线程被创建/重用等等。只需考虑队列,以及你正在推动它们。阅读Apple的并发编程指南和gcd参考也将有所帮助。

#1


18  

If you have work that you need to do on every frame, and that needs to get done before the frame is rendered, multithreading probably won't help you, unless you're willing to put a lot of effort into it.

如果你需要在每一帧上完成工作,并且需要在渲染帧之前完成,多线程可能无法帮助你,除非你愿意付出很多努力。

Maintaining a frame rate is all about time — not CPU resources, just wall time. To keep a 60fps framerate, you have 16.67 ms to do all your work in. (Actually, less than that, because SpriteKit and OpenGL need some of that time to render the results of your work.) This is a synchronous problem — you have work, you have a specific amount of time to do it in, so the first step to improving performance is to do less work or do it more efficiently.

保持帧速率几乎是时间 - 而不是CPU资源,只是时间。为了保持60fps帧速率,你有16.67毫秒来完成所有工作。(实际上,不到那个,因为SpriteKit和OpenGL需要一些时间来渲染你的工作结果。)这是一个同步问题 - 你有工作,你有一定的时间来完成这项工作,因此提高绩效的第一步是减少工作量或提高效率。

Multithreading, on the other hand, is generally for asynchronous problems — there's work you need to do, but it doesn't need to get done right now, so you can get on with the other things you need to do right now (like returning from your update method within 16 ms to keep your framerate up) and check back for the results of that work later (say, on a later frame).

另一方面,多线程通常用于异步问题 - 你需要做的工作,但它现在不需要完成,所以你可以继续你现在需要做的其他事情(比如返回)从你的更新方法在16毫秒内保持你的帧率)并检查以后的工作结果(比如,在后面的帧)。


There is a little bit of wiggle room between these two definitions, though: just about all modern iOS devices have multicore CPUs, so if you play your cards right you can fit a little bit of asynchronicity into your synchronous problem by parallelizing your workload. Getting this done, and doing it well, is no small feat — it's been the subject of serious research and investment by big game studios for years.

但是,这两个定义之间存在一些摆动空间:几乎所有现代iOS设备都有多核CPU,因此如果你正确地使用你的卡,你可以通过并行化工作负载在同步问题中适应一点异步性。完成这项工作并做得很好,这不是一件容易的事 - 多年来它一直是大型游戏工作室认真研究和投资的主题。

Take a look at the figure under "How a Scene Processes Frames of Animation" in the SpriteKit Programming Guide. That's your 16 ms clock. The light blue regions are slices of that 16 ms that Apple's SpriteKit (and OpenGL, and other system frameworks) code is responsible for. The other slices are yours. Let's unroll that diagram for a better look:

请参阅SpriteKit编程指南中“场景如何处理动画帧”下的图。那是你的16毫秒时钟。浅蓝色区域是Apple的SpriteKit(以及OpenGL和其他系统框架)代码负责的16 ms的片段。其他片是你的。让我们展开该图表以获得更好的外观:

Sprite Kit中可重用的多线程实现

If you do too much work in any of those slices, or make SpriteKit's workload too large, the whole thing gets bigger than 16 ms and your framerate drops.

如果你在任何这些切片中做了太多工作,或者使SpriteKit的工作量太大,整个事情就会超过16毫秒,你的帧率会下降。

The opportunity for threading is to get some work done on the other CPU during that same timeline. If SpriteKit's handling of actions, physics, and constraints doesn't depend on that work, you can do it in parallel with those things:

线程化的机会是在同一时间线内在另一个CPU上完成一些工作。如果SpriteKit对动作,物理和约束的处理不依赖于那些工作,那么你可以与这些工作并行执行:

Sprite Kit中可重用的多线程实现

Or, if your work needs to happen before SpriteKit runs actions & physics, but you have other work you need to do in the update method, you can send some of the work off to another thread while doing the rest of your update work, then check for results while still in your update method:

或者,如果你的工作需要在SpriteKit运行动作和物理之前发生,但你需要在更新方法中做其他工作,你可以在完成其余的更新工作时将一些工作发送到另一个线程,然后仍在您的更新方法中检查结果:

Sprite Kit中可重用的多线程实现


So how to accomplish these things? Here's one approach using dispatch groups and the assumption that actions/physics/constraints don't depend on your background work — it's totally off the top of my head, so it may not be the best. :)

那么如何完成这些事情呢?这是使用调度组的一种方法,并假设动作/物理/约束不依赖于您的后台工作 - 它完全偏离我的头脑,所以它可能不是最好的。 :)

// 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];
}

Of course, dispatch_group_wait will, as its name suggests, block execution to wait until your background work is done, so you still have that 16ms time constraint. If the foreground work (the rest of your update, plus SpriteKit's actions/physics/constraints work and any of your other work that gets done in response to those things) gets done before your background work does, you'll be waiting for it. And if the background work plus SpriteKit's rendering work (plus whatever you do in update before spawning the background work) takes longer than 16 ms, you'll still drop frames. So the trick to this is knowing your workload in enough detail to schedule it well.

当然,dispatch_group_wait将顾名思义阻止执行,直到你的后台工作完成,所以你仍然有16ms的时间限制。如果前台工作(更新的其余部分,加上SpriteKit的动作/物理/约束工作以及为响应这些事情而完成的任何其他工作)在后台工作之前完成,那么您将等待它。如果后台工作加上SpriteKit的渲染工作(加上你在生成后台工作之前在更新中做的任何事情)花费的时间超过16毫秒,你仍然会丢帧。因此,解决这个问题的方法是充分了解您的工作量以便安排好。

#2


2  

Consider a slightly different approach. Create and maintain your own queue rather than getting a system queue.

考虑一种稍微不同的方法。创建和维护自己的队列而不是获取系统队列。

a) call dispatch_queue_create to make a new queue, save this queue in your object. Use dispatch_async on that queue to run your job. You may need to synchronize if that has to complete before the next frame, etc.

a)调用dispatch_queue_create创建一个新队列,将该队列保存在对象中。在该队列上使用dispatch_async来运行您的作业。如果必须在下一帧之前完成,则可能需要同步等。

b) If you have multiple jobs, consider a concurrent queue instead of a serial queue, which may or may not make things 'faster' depending on your dependencies.

b)如果您有多个作业,请考虑并发队列而不是串行队列,这可能会也可能不会使事情“更快”,具体取决于您的依赖关系。

With GCD you're supposed to not think about threads, if new threads are created/reused etc. Just think about the queues, and what you're pushing on to them. Reading Apple's Concurrency Programming Guide and reference on gcd will also hopefully help.

使用GCD,你应该不考虑线程,如果新线程被创建/重用等等。只需考虑队列,以及你正在推动它们。阅读Apple的并发编程指南和gcd参考也将有所帮助。