Quel est le meilleur moyen (lorsque les performances comptent) d'implémenter une machine à états en C #?

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

Question

J'ai proposé les options suivantes:

Utilisation de l'instruction goto:

Start:
    goto Data
Data:
    goto Finish
Finish:
    ;

en utilisant l'instruction switch:

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

en utilisant goto et basculer ensemble:

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;
}

Je préfère la première option utilisant l'instruction goto, car elle est plus rapide et moins détaillée. Mais je ne suis pas sûr que ce soit la meilleure option. Performance sage peut-être, mais quand il s'agit de lisibilité, je ne sais pas. C'est pourquoi je pose cette question. Quelle option préférez-vous et pourquoi?

Était-ce utile?

La solution

L'avantage avec le basculement sur le goto est que vous avez l'état dans une variable, pas seulement dans le pointeur d'instruction.

Avec la méthode goto, la machine à états doit être la boucle principale qui contrôle tout le reste, car vous ne pouvez pas en sortir car vous perdriez l'état.

Avec la méthode de commutation, la machine à états est isolée et vous pouvez aller où vous voulez pour gérer les événements de l'extérieur. Lorsque vous revenez à la machine d'état, elle continue simplement là où vous vous êtes arrêté. Vous pouvez même avoir plusieurs machines d'état fonctionnant côte à côte, ce qui n'est pas possible avec la version de goto.

Je ne sais pas trop où vous allez avec la troisième alternative, elle ressemble à la première alternative avec un commutateur inutile autour d'elle.

Autres conseils

Je préfère les fonctions appelant / récursives mutuellement. Pour adapter votre exemple:

returnvalue Start() {
    return Data();
}

returnvalue Data() {
    return Finish();
}

returnvalue Finish() {
    …
}

Théoriquement, ce centre peut être complètement en ligne de sorte que la sortie du compilateur soit équivalente à votre solution goto (par conséquent, à la même vitesse). De manière réaliste, le compilateur C # / JITter ne le fera probablement pas . Mais comme la solution est beaucoup plus lisible (à mon humble avis), je ne la remplacerais par la solution goto qu'après un test très rigoureux prouvant qu'elle était vraiment inférieure en termes de débordement de la pile (pas dans cette solution simple, mais dans le cas d’automates de plus grande taille).

Même dans ce cas, je voudrais certainement adhérer à la solution goto case . Pourquoi? Parce qu'alors vos pâtes goto désordonnées sont bien encastrées dans une structure de bloc (le bloc switch ) et vos spaghettis ne vont pas écraser le reste du code, empêchant ainsi Bolognese.

En conclusion : la variante fonctionnelle est claire mais généralement sujette aux problèmes. La solution goto est en désordre. Seul goto case offre une solution à mi-chemin propre et efficace. Si les performances sont vraiment primordiales (et que l'automate est le goulot d'étranglement), optez pour la variante structurée goto case .

Il existe une quatrième option.

Utilisez un itérateur pour implémenter une machine à statuer. Voici un beau court métrage article vous montrant comment

Cependant, il présente certains inconvénients. Manipuler l'état de l'extérieur de l'itérateur n'est pas possible.

Je ne suis pas sûr non plus que ce soit très rapide. Mais vous pouvez toujours faire un test.

Si vous souhaitez diviser la logique de transition de votre machine d'état en fonctions distinctes, vous ne pouvez le faire qu'à l'aide d'instructions switch.

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

Il est également plus lisible et les frais généraux liés à l'instruction de commutation (par rapport à Goto) ne feront une différence de performances que dans de rares circonstances.

EDIT:

Vous pouvez utiliser "goto case". faire de petites améliorations de performances:

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;
} 

Cependant, vous risquez d'oublier de mettre à jour la variable d'état. Ce qui pourrait causer des bugs subtils plus tard (parce que vous avez supposé que "m_state" était défini), je suggère donc de l'éviter.

Personnellement, je préfère le second avec goto car le premier nécessitera une étape de boucle inutile (par exemple) pour aller au nouvel état

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top