この非常に単純な非同期メソッドを考えてみましょう。
static async Task myMethodAsync()
{
await Task.Delay(500);
}
これをVS2013(Roslynコンパイラー前)でコンパイルすると、生成されるステートマシンは構造体です。
private struct <myMethodAsync>d__0 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
VS2015(Roslyn)でコンパイルすると、生成されるコードは次のようになります。
private sealed class <myMethodAsync>d__1 : IAsyncStateMachine
{
...
void IAsyncStateMachine.MoveNext()
{
...
}
}
ご覧のとおり、Roslynはクラスを生成します(構造体は生成しません)。古いコンパイラの非同期/待機サポートの最初の実装(CTP2012 iの推測)も正しく覚えていれば、クラスも生成され、パフォーマンス上の理由から構造体に変更されました。 (場合によっては、ボクシングとヒープ割り当てを完全に回避できます…)( this を参照)
これがロズリンで再び変更された理由を誰もが知っていますか? (これに関する問題はありません。この変更は透過的であり、コードの動作を変更しないことを知っています。ただ興味があります)
編集:
@Damien_The_Unbeliever(およびソースコード:))からの答えimhoがすべてを説明しています。説明されているRoslynの動作は、デバッグビルドにのみ適用されます(コメントで言及されているCLRの制限のために必要です)。リリースでは、構造体も生成します(その利点をすべて備えています)。そのため、これは編集と継続の両方をサポートし、本番環境でのパフォーマンスを向上させる非常に賢いソリューションのようです。興味深いもの、参加したすべての人に感謝します!
私はこれについて何の予知も持っていませんでしたが、最近Roslynはオープンソースであるため、説明のためにコードを探しに行くことができます。
そして、ここで AsyncRewriterの60行目 で、
// The CLR doesn't support adding fields to structs, so in order to enable EnC in an async method we need to generate a class.
var typeKind = compilationState.Compilation.Options.EnableEditAndContinue ? TypeKind.Class : TypeKind.Struct;
したがって、struct
sを使用することにはある程度の魅力はありますが、 Edit and Continue がasync
メソッド内で機能することの大きな勝利が明らかにより良いオプションとして選択されました。
このようなことについて明確な答えを出すのは難しいですが(コンパイラチームの誰かが立ち寄らない限り:))、考慮できる点がいくつかあります。
構造体のパフォーマンス「ボーナス」は常にトレードオフです。基本的に、次のものが得られます。
待機中の場合、これはどういう意味ですか?まあ、実際には...何も。ステートマシンがスタック上にあるのは非常に短い期間だけです-await
はreturn
を効果的に実行するため、メソッドスタックは終了します。ステートマシンはどこかに保存する必要があり、その「どこか」は間違いなくヒープ上にあります。スタックの寿命は非同期コードにうまく適合しません:)
これとは別に、ステートマシンは、構造体を定義するためのいくつかの優れたガイドラインに違反しています。
struct
sの最大サイズは16バイトです。ステートマシンには2つのポインターが含まれており、64ビットで16バイトの制限をきちんと満たします。それとは別に、状態自体があるため、「制限」を超えます。これはbigの取り引きではありません。参照でしか渡されない可能性が高いためですが、構造体のユースケースに適合しないことに注意してください。struct
sは不変である必要があります-おそらく、これはコメントの多くを必要としません。 ステートマシンです。繰り返しますが、構造体は自動生成されたコードでプライベートなので、これは大した問題ではありませんが...struct
sは、論理的に単一の値を表す必要があります。確かにここではそうではありませんが、そもそも可変状態を持っていることから、すでにそのようなことが起こります。そしてもちろん、これはすべてクロージャーがない場合です。 await
sをトラバースするローカル(またはフィールド)がある場合、状態はさらに大きくなり、構造体を使用する有用性が制限されます。
これらすべてを考慮すると、クラスアプローチは間違いなくクリーンであり、代わりにstruct
を使用することによる顕著なパフォーマンスの向上は期待できません。関連するすべてのオブジェクトの寿命は似ているため、メモリパフォーマンスを向上させる唯一の方法は、allをstruct
s(たとえば、バッファに格納する)にすることです-これは不可能ですもちろん、一般的なケースでは。そもそもawait
を使用するほとんどの場合(つまり、非同期I/Oの作業)には、他のクラス(データバッファー、文字列など)が既に含まれています... await
ヒープ割り当てを行わずに、単に42
を返すもの。
最後に、実際のパフォーマンスの違いが本当に見られるのはベンチマークだけだと思います。そして、控えめに言っても、ベンチマークの最適化は馬鹿げたアイデアです...