実装 - c++11 コルーチン




スタックレスコルーチンは、スタックフルコルーチンとどのように異なりますか? (2)

まず、 CO2を ご覧いただきありがとうございます:)

Boost.Coroutineの doc は、スタックフルコルーチンの利点について詳しく説明しています。

スタックフルネス

スタックレスコルーチンとは対照的に 、ネストされたスタックフレーム内からスタックフルコルーチンを中断できます 。 実行は、以前中断されたコード内のまったく同じポイントで再開されます。 スタックレスコルーチンを使用すると、最上位のルーチンのみが中断されます。 そのトップレベルのルーチンによって呼び出されるルーチンは、それ自体を中断することはできません。 これにより、汎用ライブラリ内のルーチンでの一時停止/再開操作の提供が禁止されます。

一流の継続

ファーストクラスの継続は、引数として渡され、関数によって返され、後で使用されるデータ構造に保存されます。 実装によっては(たとえばC#yield)、継続に直接アクセスしたり直接操作したりすることはできません。

スタックフル性と一流のセマンティクスがなければ、いくつかの有用な実行制御フローをサポートできません(たとえば、協調的なマルチタスクまたはチェックポイント)。

それはあなたにとって何を意味しますか? たとえば、訪問者を受け取る関数があるとします。

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

スタックフルコルーチンを使用して、イテレータに変換したい場合、次のことができます。

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

しかし、スタックレスコルーチンでは、そうする方法はありません。

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

一般に、スタックフルコルーチンはスタックレスコルーチンよりも強力です。 それでは、なぜスタックレスコルーチンが必要なのでしょうか? 短い答え:効率。

スタックフルコルーチンは通常、ランタイムスタック(十分に大きくなければならない)に対応するために一定量のメモリを割り当てる必要があり、コンテキストスイッチはスタックレスのものに比べて高価です(例:Boost.Coroutineは40サイクル、CO2はわずか7スタックレスコルーチンが復元する必要があるのはプログラムカウンターだけだからです。

とはいえ、言語サポートにより、おそらくスタックフルコルーチンは、コルーチンに再帰がない限り、コンパイラが計算したスタックの最大サイズを利用できるため、メモリ使用量も改善できます。

スタックレスコルーチンについて言えば、それはランタイムスタックがないことを意味するものではなく、ホスト側と同じランタイムスタックを使用することを意味するだけなので、再帰関数も呼び出すことができます。すべての再帰はホストのランタイムスタックで発生します。 対照的に、スタックフルコルーチンでは、再帰関数を呼び出すと、コルーチンのスタックで再帰が発生します。

質問に答えるには:

  • 自動ストレージ変数(つまり、 "スタック上の"変数)の使用に制限はありますか?

いいえ。CO2のエミュレーション制限です。 言語サポートにより 、コルーチンに見える 自動ストレージ変数は、コルーチンの内部ストレージに配置されます。 コルーチンが内部的に自動ストレージ変数を使用する関数を呼び出すと、それらの変数はランタイムスタックに配置されます。 より具体的には、スタックレスコルーチンは、再開後に使用できる変数/一時ファイルのみを保持する必要があります。

明確にするために、CO2のコルーチン本体でも自動ストレージ変数を使用できます。

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

定義が await 先行しない限り。

  • スタックレスコルーチンから呼び出すことができる機能に制限はありますか?

番号。

  • スタックレスコルーチンのスタックコンテキストの保存がない場合、コルーチンの実行中に自動ストレージ変数はどこに移動しますか?

上記で回答したように、スタックレスコルーチンは、呼び出された関数で使用される自動ストレージ変数を考慮せず、通常のランタイムスタックに配置されるだけです。

疑問がある場合は、CO2のソースコードを確認するだけで、内部の仕組みを理解できる場合があります;)

バックグラウンド:

私は現在、多数(数百から数千)のスレッドを持つアプリケーションを持っているので、これを求めています。 これらのスレッドのほとんどは、ほとんどの時間アイドル状態であり、ワークアイテムがキューに入れられるのを待っています。 ワークアイテムが使用可能になると、任意の複雑な既存のコードを呼び出して処理されます。 一部のオペレーティングシステム構成では、アプリケーションがユーザープロセスの最大数を制御するカーネルパラメーターに衝突するため、ワーカースレッドの数を減らす手段を試してみたいと思います。

私の提案したソリューション:

これを実現するには、各ワーカースレッドをコルーチンに置き換えるコルーチンベースのアプローチのように思えます。 その後、実際の(カーネル)ワーカースレッドのプールによってバックアップされた作業キューを持つことができます。 アイテムが処理のために特定のコルーチンのキューに配置されると、エントリがスレッドプールのキューに配置されます。 その後、対応するコルーチンを再開し、キューに入れられたデータを処理してから、再び一時停止して、ワーカースレッドを解放して他の作業を行います。

実装の詳細:

これをどのように行うかを考えると、スタックレスコルーチンとスタックフルコルーチンの機能の違いを理解するのが困難です。 Boost.Coroutine ライブラリを使用して Boost.Coroutine フルコルーチンを使用した Boost.Coroutine ます。 概念レベルから理解するのは比較的簡単だと思います:各コルーチンについて、CPUコンテキストとスタックのコピーを維持し、コルーチンに切り替えると、その保存されたコンテキストに切り替わります(カーネルモードスケジューラのように) )。

私にはあまりはっきりしていないのは、スタックレスコルーチンがこれとどのように異なるかです。 私のアプリケーションでは、上記のワークアイテムのキューイングに関連するオーバーヘッドの量が非常に重要です。 私が見たほとんどの実装は 、新しいCO2ライブラリの ように、スタックレスコルーチンがオーバーヘッドの少ないコンテキストスイッチを提供することを示唆しています。

したがって、スタックレスコルーチンとスタックフルコルーチンの機能の違いをより明確に理解したいと思います。 具体的には、これらの質問について考えます。

  • このような参照 は、スタックフルコスタックとスタックレスコルーチンのどちらで譲歩/再開できるかに違いがあることを示唆しています。 これは事実ですか? スタックレスコルーチンでできるが、スタックレスコルーチンではできないことの簡単な例はありますか?

  • 自動ストレージ変数(つまり、 "スタック上の"変数)の使用に制限はありますか?

  • スタックレスコルーチンから呼び出すことができる機能に制限はありますか?

  • スタックレスコルーチンのスタックコンテキストの保存がない場合、コルーチンの実行中に自動ストレージ変数はどこに移動しますか?


必要なのは、ユーザーランドのスレッド/ファイバーです。通常、深いネストコールスタック(たとえば、TCP接続からのメッセージの解析)で(ファイバーで実行されている)コードを中断します。 この場合、スタックレスコンテキストスイッチングは使用できません(アプリケーションスタックはスタックレスコルーチン間で共有されます->呼び出されたサブルーチンのスタックフレームは上書きされます)。

boost.contextに基づいてユーザーランドのスレッド/ファイバーを実装するboost.fiberのようなものを使用できます。





boost-coroutine