Qual é a melhor (quando o desempenho é importante) maneira de implementar uma máquina de estado em C #?

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

Pergunta

Eu vim com as seguintes opções:

Usando a instrução goto:

Start:
    goto Data
Data:
    goto Finish
Finish:
    ;

usando a instrução switch:

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

usando Goto e alternar entre si:

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

Eu prefiro a primeira opção utilizando a instrução goto, porque é mais rápido e menos detalhado. Mas eu não tenho certeza se é a melhor opção. Em termos de desempenho, talvez, mas quando se trata de legibilidade eu não sei. É por isso que faço esta pergunta. Qual opção você prefere e por quê?

Foi útil?

Solução

A vantagem com o interruptor sobre o Goto é que você tem estado em uma variável, não apenas no ponteiro de instrução.

Com o método goto máquina estatal tem que ser o loop principal que controla tudo o resto, porque você não pode sair dele porque você perderia o estado.

Com o método chave da máquina estatal é isolado, e você pode ir a qualquer lugar que você deseja manipular eventos a partir do exterior. Quando você voltar para a máquina de estado, ele só continua onde yuu parou. Você pode até ter mais do que uma máquina de estado correndo lado a lado, algo que não é possível com a versão Goto.

Eu não tenho certeza onde você está indo com a terceira alternativa, olha só como a primeira alternativa com um interruptor inútil em torno dele.

Outras dicas

Eu prefiro mutuamente chamando / funções recursivas. Para adaptar o seu exemplo:

returnvalue Start() {
    return Data();
}

returnvalue Data() {
    return Finish();
}

returnvalue Finish() {
    …
}

Teoricamente, este pode ser completamente sequenciados de modo que a saída do compilador é equivalente à sua solução goto (daí, mesma velocidade). Realisticamente, o compilador C # / Jitter provavelmente não vai fazê-lo . Mas desde que a solução é muito mais legível (bem, IMHO), eu só iria substituí-lo com a solução goto depois de uma prova de referência muito cuidado para que ele é , de fato inferior em termos de velocidade, ou que estouros de pilha ocorrer (não nesta solução simples mas maior autômatos executar para esse problema).

Mesmo assim, eu o faria definitivamente vara para a solução goto case. Por quê? Porque, então, todo o seu bagunçado goto pasta está bem encaixada dentro de uma estrutura de bloco (o bloco switch) e seus spaghetti não vai mangle o resto do código, evitando Bolognese.

Em conclusão : a variante funcional é clara, mas em geral propensos a problemas. A solução goto é confuso. Somente goto case oferece uma solução a meio caminho limpo, eficiente. Se o desempenho é realmente fundamental (e o autômato é o gargalo de garrafa), vá para a variante goto case estruturado.

Há uma quarta opção.

Use um iterador para implementar um StateMachine. Aqui é um agradável curta artigo mostrando como

Ele tem algumas desvantagens embora. Manipulando o estado de fora do iterador não é possível.

Eu também não tenho certeza se ele é muito rápido. Mas você sempre pode fazer um teste.

Se você quiser quebrar a lógica de transição máquina de estado em funções separadas, você só pode fazê-lo usando declarações switch.

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

Também é mais legível, ea sobrecarga da instrução switch (versus Goto) só vai fazer uma diferença de desempenho em raras circunstâncias.

EDIT:

Você pode usar "goto case" para fazer pequena melhoria de desempenho:

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

No entanto, você corre o risco de esquecer de atualizar a variável de estado. Que pode causar erros sutis, mais tarde, (porque você assumiu que "m_state" foi definido), então eu sugiro evitá-lo.

Pessoalmente eu prefiro segundo com Goto desde primeiro exigirá passo laço desnecessários (por exemplo) para ir para o novo estado

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top