无叠层协程与叠层协程有何不同? [英] How do stackless coroutines differ from stackful coroutines?

查看:100
本文介绍了无叠层协程与叠层协程有何不同?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景:

我之所以问这个问题,是因为我目前有一个具有许多(数百至数千)线程的应用程序。这些线程中的大多数在大部分时间都处于空闲状态,等待将工作项放入队列中。当一个工作项可用时,然后通过调用一些任意复杂的现有代码来对其进行处理。在某些操作系统配置上,应用程序会遇到控制最大用户进程数的内核参数,因此,我想尝试减少工作线程数的方法。

I'm asking this because I currently have an application with many (hundreds to thousands) of threads. Most of those threads are idle a great portion of the time, waiting on work items to be placed in a queue. When a work item comes available, it is then processed by calling some arbitrarily-complex existing code. On some operating system configurations, the application bumps up against kernel parameters governing the maximum number of user processes, so I'd like to experiment with means to reduce the number of worker threads.

我建议的解决方案:

这似乎是基于协程的方法,我用协程替换每个工作线程的地方,将有助于实现这一目标。然后,我可以有一个由实际(内核)工作线程池支持的工作队列。将项目放置在特定协程的队列中进行处理时,条目将放置在线程池的队列中。然后它将恢复相应的协程,处理其排队的数据,然后再次将其暂停,以释放工作线程来执行其他工作。

It seems like a coroutine-based approach, where I replace each worker thread with a coroutine, would help to accomplish this. I can then have a work queue backed by a pool of actual (kernel) worker threads. When an item is placed in a particular coroutine's queue for processing, an entry would be placed into the thread pool's queue. It would then resume the corresponding coroutine, process its queued data, and then suspend it again, freeing up the worker thread to do other work.

实现细节:

在考虑如何做到这一点时,我很难理解无栈协程和有栈协程之间的功能差异。我有一些使用 Boost.Coroutine 的堆栈式协程的经验。图书馆。我发现从概念上理解相对容易:对于每个协程,它维护CPU上下文和堆栈的副本,​​并且当您切换到协程时,它将切换到该保存的上下文(就像内核模式调度程序那样)。

In thinking about how I would do this, I'm having trouble understanding the functional differences between stackless and stackful coroutines. I have some experience using stackful coroutines using the Boost.Coroutine library. I find it's relatively easy to comprehend from a conceptual level: for each coroutine, it maintains a copy of the CPU context and stack, and when you switch to a coroutine, it switches to that saved context (just like a kernel-mode scheduler would).

我不太清楚的是无堆栈协程与之有何不同。在我的应用程序中,与上述工作项排队相关的开销非常重要。我见过的大多数实现,例如新的CO2库都建议无堆栈协程提供开销更低的上下文切换。

What is less clear to me is how a stackless coroutine differs from this. In my application, the amount of overhead associated with the above-described queuing of work items is very important. Most implementations that I've seen, like the new CO2 library suggest that stackless coroutines provide much lower-overhead context switches.

因此,我想更清楚地了解无栈协程和有栈协程之间的功能差异。具体来说,我想到了以下问题:

Therefore, I'd like to understand the functional differences between stackless and stackful coroutines more clearly. Specifically, I think of these questions:


  • 此类参考文献指出,区别在于您可以在有堆栈的协程与无堆栈的协程中进行屈服/恢复。是这样吗是否有一个简单的示例,说明我可以在堆栈式协程中执行某些操作,但不能在无堆栈的协程中执行?

  • References like this one suggest that the distinction lies in where you can yield/resume in a stackful vs. stackless coroutine. Is this the case? Is there a simple example of something that I can do in a stackful coroutine but not in a stackless one?

使用自动存储是否有任何限制?变量(即堆栈上的变量)?

Are there any limitations on the use of automatic storage variables (i.e. variables "on the stack")?

我可以从无堆栈协程调用哪些函数?

Are there any limitations on what functions I can call from a stackless coroutine?

如果没有为无堆栈协程保存堆栈上下文,那么当协程运行时自动存储变量会移到哪里?

If there is no saving of stack context for a stackless coroutine, where do automatic storage variables go when the coroutine is running?

推荐答案

首先,感谢您查看 CO2 :)

Boost.Coroutine 文档描述了很好地堆叠式协程的优点:

The Boost.Coroutine doc describes the advantage of stackful coroutine well:


可堆叠性 >

与无堆栈的协程相比,可以从嵌套的堆栈框架中悬挂大量的协程
。执行将在
中与之前被暂停的代码完全相同的位置恢复。使用
的无堆栈协程,只能暂停顶层例程。
顶层例程调用的任何例程本身都不会暂停。
这禁止在
a通用库中的例程中提供暂停/恢复操作。

In contrast to a stackless coroutine a stackful coroutine can be suspended from within a nested stackframe. Execution resumes at exactly the same point in the code where it was suspended before. With a stackless coroutine, only the top-level routine may be suspended. Any routine called by that top-level routine may not itself suspend. This prohibits providing suspend/resume operations in routines within a general-purpose library.

一流的延续

一流的延续可以作为
的参数传递,由函数返回并存储到
的数据结构中,以备后用。在某些实现中(例如C#yield),不能直接访问或直接操作
延续。

A first-class continuation can be passed as an argument, returned by a function and stored in a data structure to be used later. In some implementations (for instance C# yield) the continuation can not be directly accessed or directly manipulated.

没有堆栈性和一流的语义,执行一些有用的操作无法支持
控制流(例如,协作
多任务处理或检查点)。

Without stackfulness and first-class semantics, some useful execution control flows cannot be supported (for instance cooperative multitasking or checkpointing).

这是什么意思您?例如,假设您有一个访问者函数:

What does that mean to you? for example, imagine you have a function that takes a visitor:

template<class Visitor>
void f(Visitor& v);

您希望将其转换为迭代器,并使用大量协程,您可以:

You want to transform it to iterator, with stackful coroutine, you can:

asymmetric_coroutine<T>::pull_type pull_from([](asymmetric_coroutine<T>::push_type& yield)
{
    f(yield);
});

但是对于无堆栈的协程,则无法这样做:

But with stackless coroutine, there's no way to do so:

generator<T> pull_from()
{
    // yield can only be used here, cannot pass to f
    f(???);
}

通常,堆栈式协程比无堆栈式协程更强大。
那么,为什么我们要无堆栈的协程?简短的答案:效率。

In general, stackful coroutine is more powerful than stackless coroutine. So why do we want stackless coroutine? short answer: efficiency.

堆栈协程通常需要分配一定数量的内存以适应其运行时堆栈(必须足够大),并且上下文切换是与无堆叠的相比,价格更高Boost.Coroutine在我的机器上平均需要40个周期,而CO2平均仅需要7个周期,因为无堆栈协程需要还原的唯一东西就是程序计数器。

Stackful coroutine typically needs to allocate a certain amount of memory to accomodate its runtime-stack (must be large enough), and the context-switch is more expensive compared to the stackless one, e.g. Boost.Coroutine takes 40 cycles while CO2 takes just 7 cycles in average on my machine, because the only thing that a stackless coroutine needs to restore is the program counter.

他说,在语言支持下,只要协程中没有递归,可能有堆栈的协程也可以利用编译器计算的最大堆栈大小,因此也可以提高内存使用率。

That said, with language support, probably stackful coroutine can also take the advantage of the compiler-computed max-size for the stack as long as there's no recursion in the coroutine, so the memory usage can also be improved.

说到无堆栈协程,请记住这并不意味着根本没有运行时堆栈,仅意味着它使用与主机端相同的运行时堆栈,所以您也可以调用递归函数,只是所有递归都将在主机的运行时堆栈上进行。相反,使用堆栈式协程,当您调用递归函数时,递归将发生在协程自身的堆栈上。

Speaking of stackless coroutine, bear in mind that it doesn't mean that there's no runtime-stack at all, it only means that it uses the same runtime-stack as the host side, so you can call recursive functions as well, just that all the recursions will happen on the host's runtime-stack. In contrast, with stackful coroutine, when you call recursive functions, the recursions will happen on the coroutine's own stack.

回答以下问题:


  • 使用自动存储变量
    (即堆栈上的变量)是否有任何限制?

否。这是二氧化碳的模拟限制。在语言支持下,<协程可见的自动存储变量将被放置在协程的内部存储中。请注意我对协程可见的强调,如果协程调用一个在内部使用自动存储变量的函数,则这些变量将放置在运行时堆栈中。更具体地说,无堆栈协程只需要保留恢复后可以使用的变量/临时变量。

No. It's the emulation limitation of CO2. With language support, the automatic storage variables visible to the coroutine will be placed on the coroutine's internal storage. Note my emphasis on "visible to the coroutine", if the coroutine calls a function that uses automatic storage variables internally, then those variables will be placed on the runtime-stack. More specifically, stackless coroutine only has to preserve the variables/temporaries that can be used after resumed.

需要明确的是,您可以在CO2的协程主体中使用自动存储变量作为好:

To be clear, you can use automatic storage variables in CO2's coroutine body as well:

auto f() CO2_RET(co2::task<>, ())
{
    int a = 1; // not ok
    CO2_AWAIT(co2::suspend_always{});
    {
        int b = 2; // ok
        doSomething(b);
    }
    CO2_AWAIT(co2::suspend_always{});
    int c = 3; // ok
    doSomething(c);
} CO2_END

只要定义不位于任何

As long as the definition does not precede any await.


  • 我可以从
    无堆栈协程调用哪些函数吗?

否。


  • 如果有对于没有堆栈的协程,不会节省堆栈上下文,
    当协程运行
    时,自动存储变量会去哪里?

以上回答,无栈协程并不关心被调用函数中使用的自动存储变量,它们只会放在正常的运行时堆栈中。

Answered above, a stackless coroutine doesn't care about the automatic storage variables used in the called functions, they'll just be placed on the normal runtime-stack.

如果有任何疑问,只需检查一下CO2的源代码,它可以帮助您了解幕后的技巧;)

If you have any doubt, just check CO2's source code, it may help you understand the mechanics under the hood ;)

这篇关于无叠层协程与叠层协程有何不同?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆