1. ホーム
  2. c++

[解決済み] C++20のスタックレスコルーチンは問題か?

2023-05-30 11:15:53

質問

以下の内容から、C++20 のコルーチンはスタックレスになるようです。

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

いろいろな理由で心配です。

  1. 組み込みシステムにおいて、ヒープ割り当てはしばしば許容されません。
  2. 低レベルのコードでは、co_awaitのネストは有用でしょう(スタックレスco-routineがこれを許すとは思えません)。

スタックレス・コルーチンでは、トップレベルのルーチンのみが中断される可能性があります。 中断されます。そのトップレベルルーチンによって呼び出されたルーチンは、それ自身はサスペンドできません。 はサスペンドできません。これは、汎用ライブラリ内のルーチンでサスペンド/レジューム操作を提供することを禁止しています。 ルーチンにサスペンド/レジューム操作を提供することはできません。

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

  1. カスタムアロケータとメモリプールが必要なため、より冗長なコードになります。

  2. タスクがオペレーティングシステムがメモリを割り当てるのを待つ場合、より遅くなります (メモリプールなし)。

これらの理由を考えると、現在のコルーチンが何であるかについて私が非常に間違っていることを本当に望んでいます。

質問には3つの部分があります。

  1. なぜ C++ はスタックレス・コルーチンを使うことにしたのでしょうか?
  2. スタックレスコルーチンの状態を保存するためのアロケーションについて。コルーチン生成のために通常使用されるヒープ割り当てを避けるためにalloca()を使用することができますか。

コルーチンの状態はヒープ上に非配列の 演算子newによってヒープ上に確保されます。 https://en.cppreference.com/w/cpp/language/coroutines

  1. c++ のコルーチンについての私の仮定は間違っていますか、なぜですか?

EDITです。

今、コルーチンに関するcppconのトークを調べているのですが、もし自分の疑問に対する答えが見つかれば投稿します(今のところ何もありません)。

CppCon 2014に参加しました。Gor Nishanov "await 2.0: Stackless Resumable Functions"

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 coroutines" と呼びます。

動的割り当てについて

Cppreferenceでは、標準よりも緩い用語が使われることがあります。 co_await は機能として "requires" 動的割り当てを必要とします。この割り当てがヒープから来るのか、メモリの静的ブロックから来るのか、あるいは何であれ、割り当ての提供者にとっては重要なことなのです。このような割り当ては、任意の状況で省略することができますが、標準では明記されていないため、任意の co_await コルーチンが動的にメモリを割り当てることができると仮定する必要があります。

co_awaitコルーチンは、ユーザーがコルーチンの状態のために割り当てを提供するためのメカニズムを持っています。そのため、ヒープ/フリーストアの割り当てを、あなたが望むメモリの任意の特定のプールに置き換えることができます。

co_await を機能としてうまく設計しています。 を削除する の使用時点から冗長性を排除しています。 co_await -可能なオブジェクトや機能に対してです。その co_await 機構は信じられないほど複雑で、いくつかのタイプのオブジェクトの間で多くの相互作用があります。しかし、サスペンド/レジュームポイントにおいて、それは 常に のように見えます。 co_await <some expression> . awaitableオブジェクトとプロミスにアロケータのサポートを追加することは、いくつかの冗長性を必要としますが、その冗長性はそれらが使用される場所の外に住んでいます。

使用方法 alloca をコルーチンに使用することは、...非常に不適切です。 最も の使用法としては非常に不適切です。 co_await . この機能をめぐる議論ではそれを隠そうとしますが、実際のところは co_await は非同期で使用するために設計されています。それは、関数の実行を停止し、その関数の再開を潜在的に別のスレッドでスケジュールし、最終的に生成された値を、コルーチンを呼び出したコードから多少離れた受信コードに渡すというものです。

alloca はその特定のユースケースには適切ではありません。なぜなら、コルーチンの呼び出し元は、値が他のスレッドによって生成されることができるように、何でもすることを許可/奨励されているからです。によって割り当てられたスペースは alloca によって割り当てられたスペースはもはや存在せず、それはその中に住んでいるコルーチンにとってちょっと悪いことです。

スレッド スケジューリング、ミューテックス、および他のものは、値を提供する非同期プロセスから値を取得するのにかかる時間は言うまでもなく、コルーチンの再開を適切にスケジュールするためにしばしば必要とされます。したがって、動的割り当てが必要であるという事実は、この場合、実際には重要な考慮事項ではありません。

さて、ここで があります。ジェネレータのユースケースは、本質的に関数を一時停止して値を返し、その後、関数が中断したところから再開して、新しい値を返す可能性がある場合です。これらのシナリオでは、コルーチンを呼び出す関数のスタックは確かにまだ残っています。

co_await はそのようなシナリオをサポートします (ただし co_yield はそのようなシナリオをサポートしますが、少なくとも標準の観点からは、あまり最適ではない方法でそれを行います。この機能はアップ アンド アウトのサスペンド用に設計されているため、サスペンド ダウン コルーチンに変えると、動的である必要のない動的な割り当てが行われることになります。

コンパイラーがジェネレーターの使用パターンを検出できるほど賢ければ、動的割り当てを削除し、ローカルスタックにスペースを割り当てるだけでよいのですが、繰り返しますが、これが標準が動的割り当てを必要としない理由です。しかし、繰り返しになりますが、これはコンパイラーが ができることです。 ができることであり、しなければならないことではありません。

この場合 alloca -に基づく割り当てが適切でしょう。

どのようにして標準になったか

手短に言えば、標準になったのは、その背後にいる人々が仕事をしたからであり、代替案の背後にいる人々はそうではなかったからです。

どのようなコルーチンのアイデアも複雑で、それらに関する実装可能性についての疑問が常に存在します。たとえば、" 再開可能な関数 の提案は素晴らしく、私はそれを標準規格で見るのが大好きでした。しかし、実際には誰も を実装していません。 をコンパイラーで実装した人はいませんでした。ですから、誰もそれが実際にできることだと証明することができなかったのです。確かに、それは は聞こえます。 と聞こえるかもしれませんが、だからといって は実装可能であることを意味しません。

リメンバー 前回何が起こったか 実装可能であるかのように聞こえる(" sounds implementable")」が、機能を採用するための基準として使用されました。

実装可能かどうかわからないものを標準化したくはないでしょう。そして、それが実際に意図された問題を解決するかどうかがわからない場合は、何かを標準化したくはないでしょう。

Gor Nishanov 氏と Microsoft の彼のチームは、このような問題を解決するための実装に取り組みました。 co_await . 彼らはこれを を行い、実装を洗練させたりしました。他の人々は彼らの実装を実際の生産コードで使用し、その機能性に非常に満足しているようでした。Clang はそれを実装しました。私が個人的に好きでないのと同じように、このような co_await 成熟した 機能です。

対照的に、1 年前に競合するアイデアとして持ち上がった "core coroutines" の代替案と co_await との競合案として1年前に持ち上がった代替案は、支持を得ることができませんでした。 を実装することが困難であったためです。 . そのため co_await が採用された理由は、実績があり、成熟した、健全なツールであり、人々がそれを望み、コードを改善する能力が実証されていたからです。

co_await は万人向けではありません。個人的には、私の使用例ではファイバーの方がずっとうまくいくので、あまり使うことはないでしょう。しかし、特定の使用例であるアップ・アンド・アウト・サスペンションには非常に適しています。