Каков наилучший (когда важна производительность) способ реализации конечного автомата в 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:

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 конечный автомат изолирован, и вы можете отправиться куда угодно для обработки событий извне. Когда вы возвращаетесь к конечному автомату, он просто продолжается там, где вы остановились. Вы даже можете иметь более одного конечного автомата, работающего бок о бок, что невозможно в версии goto.

Я не уверен, куда вы идете с третьей альтернативой, она выглядит как первая альтернатива с бесполезным переключателем вокруг нее.

Другие советы

Я предпочитаю взаимно вызывающие / рекурсивные функции. Чтобы адаптировать ваш пример:

returnvalue Start() {
    return Data();
}

returnvalue Data() {
    return Finish();
}

returnvalue Finish() {
    …
}

Теоретически, это может быть полностью встроенным, так что вывод компилятора эквивалентен вашему решению goto (следовательно, такая же скорость). Реально, компилятор C # / JITter, вероятно, выиграл & # 8217; это . Но так как решение намного более читабельно (ну, 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, так как первый потребует ненужного шага цикла (например), чтобы перейти в новое состояние

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top