C#でステートマシンを実装するのに最適な(パフォーマンスが重要な場合)方法は何ですか?

StackOverflow https://stackoverflow.com/questions/1617894

質問

次のオプションを思いつきました:

gotoステートメントの使用:

Start:
    goto Data
Data:
    goto Finish
Finish:
    ;

switchステートメントの使用:

switch(m_state) {
    case State.Start:
        m_state = State.Data;
        break;
    case State.Data:            
        m_state = State.Finish;
        break;
    case State.Finish:
        break;
}

gotoを使用して一緒に切り替える:

switch(m_state) {
    case State.Start:
        goto case State.Data2;
    case State.Data1:
        goto case State.Finish;
    case State.Data2:
        m_state = State.Data1;
        //call to a function outside the state machine
        //that could possibly change the state
        break;
    case State.Finish:
        break;
}

gotoステートメントを使用した最初のオプションは、より高速で冗長性が少ないため好まれます。しかし、それが最良の選択肢かどうかはわかりません。賢明なパフォーマンスかもしれませんが、読みやすさに関してはわかりません。だから私はこの質問をします。どのオプションを好むのですか?

役に立ちましたか?

解決

gotoを超えるスイッチの利点は、命令ポインタだけでなく、変数に状態があることです。

gotoメソッドでは、ステートマシンが他のすべてを制御するメインループである必要があります。ステートを失うため、ステップマシンから抜け出すことはできません。

switchメソッドを使用すると、ステートマシンが分離され、外部からイベントを処理する任意の場所に移動できます。ステートマシンに戻ると、yuuが中断したところから継続します。 gotoバージョンでは不可能な、複数のステートマシンを並べて実行することもできます。

3番目の選択肢をどこで使用するのかわかりません。これは、最初の選択肢のように見えますが、その周りに無駄なスイッチがあります。

他のヒント

相互呼び出し/再帰関数が好きです。あなたの例を適応するには:

returnvalue Start() {
    return Data();
}

returnvalue Data() {
    return Finish();
}

returnvalue Finish() {
    …
}

理論的には、コンパイラの出力が goto ソリューションと同等になるように、この を完全にインライン化できます(したがって、同じ速度)。現実的には、 C#コンパイラ/ JITterはおそらく勝ちません’それ。しかし、ソリューションは非常に読みやすいので(まあ、IMHO)、非常に慎重なベンチマークが 条件が実際に劣っていることを証明した後、 goto ソリューションに置き換えますまたは、スタックオーバーフローが発生します(この単純なソリューションではなく、より大きなオートマトンがこの問題に遭遇します)。

それでも、 goto case ソリューションに固執する 。どうして?乱雑な goto パスタ全体がブロック構造( switch ブロック)の中によく入れられており、スパゲッティが残りのコードを壊さないため、ボロネーゼ。

結論:機能のバリエーションは明確ですが、一般的に問題が発生しやすいです。 goto ソリューションは面倒です。 goto case のみが、中間的なクリーンで効率的なソリューションを提供します。パフォーマンスが本当に重要な場合(およびオートマトンがボトルネックである場合)、構造化された goto case バリアントを選択します。

4番目のオプションがあります。

イテレータを使用して、ステートマシンを実装します。 素敵な短い記事で方法を説明

ただし、いくつかの欠点があります。反復子の外部から状態を操作することはできません。

それが非常に速いかどうかもわかりません。ただし、いつでもテストを実行できます。

ステートマシンの遷移ロジックを個別の機能に分割したい場合は、switchステートメントを使用してのみ実行できます。

switch(m_state) {
        case State.Start:
                m_state = State.Data;
                break;
        case State.Data:                        
                m_state = ComputeNextState();
                break;
        case State.Finish:
                break;
} 

読みやすく、またswitchステートメントのオーバーヘッド(Gotoに対して)は、まれな状況でのみパフォーマンスの違いを生じます。

編集:

「goto case」を使用できますパフォーマンスを少し改善するには:

switch(m_state) {
        case State.Start:
                m_state = State.Data; // Don't forget this line!
                goto case State.Data;
        case State.Data:                        
                m_state = ComputeNextState();
                break;
        case State.Finish:
                break;
} 

ただし、状態変数の更新を忘れる危険があります。後で微妙なバグが発生する可能性があります(「m_state」が設定されていると仮定したため)、回避することをお勧めします。

個人的には、gotoを使用した2番目の方法の方が好きです。最初の方法では、新しい状態に移行するために(たとえば)不要なループステップが必要になるためです

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top