无栈C ++ 20协程是否有问题? [英] Are stackless C++20 coroutines a problem?

查看:204
本文介绍了无栈C ++ 20协程是否有问题?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

基于以下内容,看来C ++ 20中的协程将是无堆栈的.

Based on the following it looks like coroutines in C++20 will be stackless.

https://en.cppreference.com/w/cpp/language/coroutines

我担心的原因有很多:

  1. 在嵌入式系统上,堆分配通常是不可接受的.
  2. 在低级代码中,co_await的嵌套很有用(我不相信无栈协程允许这样做).

对于无堆栈协程,只有顶层例程可以 暂停.该顶级例程调用的任何例程可能本身都不是 暂停.这禁止在以下位置提供暂停/继续操作 通用库中的例程.

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.

https://www.boost.org/doc/libs/1_57_0/libs/coroutine/doc/html/coroutine/intro.html#coroutine.intro.stackfulness

  1. 更多详细代码,因为需要自定义分配器和内存池.

  1. More verbose code because of the need for custom allocators and memory pooling.

如果任务等待操作系统为它分配一些内存(没有内存池),则速度会变慢.

Slower if the task waits for the operating system to allocate it some memory (without memory pooling).

基于这些原因,我真的希望我对当前的协程是错误的.

Given these reasons, I'm really hoping I'm very wrong about what the current coroutines are.

问题包括三个部分:

  1. C ++为什么会选择使用无堆栈协程?
  2. 关于在无堆栈协程中保存状态的分配.我可以使用alloca()来避免通常用于协程创建的任何堆分配.

协程状态通过非数组分配在堆上 运算符new. https://en.cppreference.com/w/cpp/language/coroutines

coroutine state is allocated on the heap via non-array operator new. https://en.cppreference.com/w/cpp/language/coroutines

  1. 我对c ++协程的假设是错误的,为什么?

我现在正在为协程进行cppcon讨论,如果我对自己的问题有任何答案,我将其发布(到目前为止没有任何内容).

I'm going through the cppcon talks for the coroutines now, if I find any answers to my own question I'll post it (nothing so far).

CppCon 2014:Gor Nishanov等待2.0:无堆栈可恢复函数"

https://www.youtube.com/watch?v=KUhSjfSbINE

CppCon 2016:James McNellis"C ++协程简介"

https://www.youtube.com/watch?v=ZTqHjjm86Bw

推荐答案

转发:当这篇文章只说协程"时,我指的是协程的概念,而不是特定的C + +20功能.在谈论此功能时,我将其称为"co_await"或"co_await协程".

Forward: When this post says just "coroutines", I am referring to the concept of a coroutine, not the specific C++20 feature. When talking about this feature, I will refer to it as "co_await" or "co_await coroutines".

Cpreference有时使用比标准宽松的术语. co_await作为一项功能需要"动态分配;此分配是来自堆还是来自静态内存块,或者与分配提供者有关.可以在任意情况下取消这种分配,但是由于该标准并未明确说明它们,因此您仍然必须假定任何co_await协程都可以动态分配内存.

Cppreference sometimes uses looser terminology than the standard. co_await as a feature "requires" dynamic allocation; whether this allocation comes from the heap or from a static block of memory or whatever is a matter for the provider of the allocation. Such allocations can be elided in arbitrary circumstances, but since the standard does not spell them out, you still have to assume that any co_await coroutine may dynamically allocate memory.

co_await协程确实具有供用户为协程状态提供分配的机制.因此,您可以将堆/空闲存储分配替换为您喜欢的任何特定内存池.

co_await coroutines do have mechanisms for users to provide allocation for the coroutine's state. So you can substitute the heap/free store allocation for any particular pool of memory you prefer.

co_await作为一项功能经过精心设计,可以从删除冗长性的角度出发,用于任何支持co_await的对象和功能. co_await机制极其复杂和复杂,几种类型的对象之间存在许多交互.但是在暂停/恢复点,它总是 co_await <some expression>.在等待的对象和Promise中添加分配器支持需要一定的冗长性,但是冗长性生活在使用这些东西的地方之外.

co_await as a feature is well-designed to remove verbosity from the point of use for any co_await-able objects and functionality. The co_await machinery is incredibly complicated and intricate, with lots of interactions between objects of several types. But at the suspend/resume point, it always looks like co_await <some expression>. Adding allocator support to your awaitable objects and promises requires some verbosity, but that verbosity lives outside of the place where those things get used.

alloca用于协程将非常不适合大多数co_await的使用.尽管围绕此功能的讨论试图将其隐藏,但事实是co_await作为一项功能是专为异步使用而设计的.那是它的预期目的:停止某个函数的执行并将该函数的恢复安排在另一个线程上,然后将最终生成的值扩展到某些接收代码,这些代码可能与调用协程的代码有些许距离.

Using alloca for a coroutine would be... highly inappropriate for most uses of co_await. While the discussion around this feature tries to hide it, the fact of the matter is that co_await as a feature is designed for asynchronous use. That's its intended purpose: to halt the execution of a function and schedule that function's resumption on potentially another thread, then shepherding any eventually generated value to some receiving code which may be somewhat distant from the code which invoked the coroutine.

alloca不适合该特定用例,因为协程的调用者被允许/被鼓励去做任何事情,以便该值可以由其他线程生成.因此,alloca分配的空间将不再存在,这对于其中生活的协程来说是一种不利.

alloca is not appropriate for that particular use case, since the caller of the coroutine is allowed/encouraged to go do whatever so that the value can be generated by some other thread. The space allocated by alloca would therefore no longer exist, and that is kind of bad for the coroutine that lives in it.

还请注意,在这种情况下,分配性能通常会因其他注意事项而相形见thread:线程调度,互斥锁和其他通常需要适当安排协程恢复的时间,更不用说获得价值所需的时间了从任何异步过程正在提供它.因此,在这种情况下,实际上不需要考虑动态分配的事实.

Also note that allocation performance in such a scenario will generally be dwarfed by other considerations: thread scheduling, mutexes, and other things will often be needed to properly schedule the coroutine's resumption, not to mention the time it takes to get the value from whatever asynchronous process is providing it. So the fact that a dynamic allocation is needed is not really a substantial consideration in this case.

现在,在某些情况下,原地分配是合适的.生成器用例用于以下情况:您要实质上暂停函数并返回值,然后在函数停止的地方取值并可能返回新值.在这些情况下,调用协程的函数的堆栈肯定仍然存在.

Now, there are circumstances where in-situ allocation would be appropriate. Generator use cases are for when you want to essentially pause a function and return a value, then pick up where the function left off and potentially return a new value. In these scenarios, the stack for the function which invokes the coroutine will certainly still be around.

co_await支持这种情况(尽管是co_yield),但是至少在标准方面,它不是最佳选择.由于该功能是专为上下悬挂式设计的,因此将其转换为向下悬挂的协程具有这样的效果:无需动态即可具有这种动态分配.

co_await supports such scenarios (though co_yield), but it does so in a less-than-optimal way, at least in terms of the standard. Because the feature is designed for up-and-out suspension, turning it into a suspend-down coroutine has the effect of having this dynamic allocation that doesn't need to be dynamic.

这就是为什么该标准不需要动态分配的原因;如果编译器足够聪明以检测生成器的使用模式,则它可以删除动态分配,而只需在本地堆栈上分配空间.但是,这又是编译器 可以做的,不是必须要做的.

This is why the standard does not require dynamic allocation; if a compiler is smart enough to detect a generator pattern of usage, then it can remove the dynamic allocation and just allocate the space on the local stack. But again, this is what a compiler can do, not must do.

在这种情况下,基于alloca的分配将是适当的.

In this case, alloca-based allocation would be appropriate.

简短的版本是它加入了标准,因为它背后的人投入了工作,而替代方案的人却没有.

The short version is that it got into the standard because the people behind it put in the work, and the people behind the alternatives did not.

任何协程的想法都很复杂,关于它们的可实施性总是存在疑问.例如,"可恢复功能建议看起来不错,我很希望在标准中看到它.但是实际上没有人在编译器中实现.因此,没有人能证明这实际上是您可以做的事情.哦,可以,它听起来可以实现,但这并不意味着它 是可以实现的.

Any coroutine idea is complicated, and there will always be questions about implementability with regard to them. For example, the "resumeable functions" proposals looked great, and I would have loved to see it in the standard. But nobody actually implemented it in a compiler. So nobody could prove that it was actually a thing you could do. Oh sure, it sounds implementable, but that doesn't mean it is implementable.

记住上次发生的情况将可实施的声音"用作采用功能的基础.

Remember what happened the last time "sounds implementable" was used as the basis for adopting a feature.

如果您不知道某些事情可以实现,则不想将其标准化.而且,如果您不知道某事物是否确实解决了预期的问题,那么您就不想对其进行标准化.

You don't want to standardize something if you don't know it can be implemented. And you don't want to standadize something if you don't know if it actually solves the intended problem.

Gor Nishanov和他的Microsoft团队投入了实施co_await的工作.他们花了来做到这一点,完善了其实现方式等.其他人在实际的生产代码中使用了其实现,并且对其功能似乎很满意. Clang甚至实现了它.尽管我个人不喜欢它,但不可否认的是co_await成熟功能.

Gor Nishanov and his team at Microsoft put in the work to implement co_await. They did this for years, refining their implementation and the like. Other people used their implementation in actual production code and seemed quite satisfied with its functionality. Clang even implemented it. As much as I personally don't like it, it is undeniable that co_await is a mature feature.

相比之下,一年前随着与co_await竞争的想法而提出的``核心协程''替代品未能获得吸引力部分原因是难以实施.这就是为什么co_await被采用的原因:因为它是人们想要的,经过验证的,成熟且完善的工具,并且具有改进代码的能力.

By contrast, the "core coroutines" alternatives that were brought up a year ago as competing ideas with co_await failed to gain traction in part because they were difficult to implement. That's why co_await was adopted: because it was a proven, mature, and sound tool that people wanted and had the demonstrated ability to improve their code.

co_await并不适合所有人.就我个人而言,我可能不会使用太多,因为光纤在我的用例中效果更好.但这对于它的特定用例非常有用:向上和向下暂停.

co_await is not for everyone. Personally, I will likely not use it much, as fibers work much better for my use cases. But it is very good for its specific use case: up-and-out suspension.

这篇关于无栈C ++ 20协程是否有问题?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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