Каков наилучший (когда важна производительность) способ реализации конечного автомата в C #? - PullRequest
3 голосов
/ 24 октября 2009

Я придумал следующие варианты:

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

Ответы [ 5 ]

3 голосов
/ 24 октября 2009

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

returnvalue Start() {
    return Data();
}

returnvalue Data() {
    return Finish();
}

returnvalue Finish() {
    …
}

Теоретически, этот может быть полностью встроенным, так что вывод компилятора эквивалентен вашему goto решению (следовательно, такая же скорость). Реально, компилятор C # / JITter, вероятно, не сделает этого . Но так как решение намного более читабельно (ну, IMHO), я бы заменил его решением goto только после очень тщательного теста, доказывающего, что оно действительно действительно уступает по скорости, или этот стек происходят переполнения (не в этом простом решении, но в этой задаче встречаются более крупные автоматы).

Даже тогда я бы определенно придерживался решения goto case. Зачем? Потому что тогда вся ваша грязная паста goto будет хорошо заключена в блочную структуру (блок switch), и ваши спагетти не будут портить остальную часть кода, предотвращая Болоньезе.

В заключение : функциональный вариант понятен, но в целом подвержен проблемам. Решение goto грязное. Только goto case предлагает наполовину чистое, эффективное решение. Если производительность действительно имеет первостепенное значение (а автомат - горлышко бутылки), перейдите к структурированному варианту goto case.

2 голосов
/ 24 октября 2009

Есть 4-й вариант.

Используйте итератор для реализации машины состояний. Вот хорошая короткая статья , показывающая, как

Хотя и имеет некоторые недостатки. Управление состоянием извне итератора невозможно.

Я тоже не уверен, что это очень быстро. Но вы всегда можете сделать тест.

2 голосов
/ 24 октября 2009

Если вы когда-нибудь захотите разбить логику перехода конечного автомата на отдельные функции, вы можете сделать это только с помощью операторов 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) будут иметь значение только для производительности в редких случаях.

EDIT:

Вы можете использовать «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»), поэтому я бы посоветовал избегать этого.

2 голосов
/ 24 октября 2009

Преимущество переключения на goto состоит в том, что у вас есть состояние в переменной, а не только в указателе инструкций.

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

С помощью метода switch конечный автомат изолирован, и вы можете отправиться куда угодно для обработки событий извне. Когда вы возвращаетесь к конечному автомату, он просто продолжается там, где вы остановились. Вы даже можете иметь более одного конечного автомата, работающего рядом, что невозможно в версии goto.

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

0 голосов
/ 24 октября 2009

Лично я предпочитаю второй с goto, так как первый потребует ненужного шага цикла (например), чтобы перейти в новое состояние

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...