By the below link to Compiler Explorer you get two versions of the same finite state machine: one as a switch-statement
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#define MORE
//#define ENTER
volatile uint8_t r = 0;
volatile uint8_t x = 0;
namespace {
struct FSM {
enum class State : uint8_t {Test0, Test1, Test2, Test3, Test4
#ifdef MORE
, Test5, Test6, Test7
#endif
};
inline static void process(const uint8_t b) {
const auto oldState = mState;
switch(mState) {
case State::Test0:
if (b == 0) {
mState = State::Test1;
}
break;
case State::Test1:
if (b == 1) {
mState = State::Test2;
}
break;
case State::Test2:
if (b == 2) {
mState = State::Test3;
}
break;
case State::Test3:
if (b == 3) {
mState = State::Test4;
}
break;
case State::Test4:
if (b == 4) {
#ifdef MORE
mState = State::Test5;
#else
mState = State::Test0;
#endif
}
break;
#ifdef MORE
case State::Test5:
if (b == 5) {
mState = State::Test6;
}
break;
case State::Test6:
if (b == 6) {
mState = State::Test7;
}
break;
case State::Test7:
if (b == 7) {
mState = State::Test0;
}
break;
#endif
}
#ifdef ENTER
if (oldState != mState) {
switch(mState) {
case State::Test0:
x = 40;
break;
case State::Test1:
x = 41;
break;
case State::Test2:
x = 42;
break;
case State::Test3:
x = 43;
break;
case State::Test4:
x = 44;
break;
#ifdef MORE
case State::Test5:
x = 45;
break;
case State::Test6:
x = 46;
break;
case State::Test7:
x = 47;
break;
#endif
}
}
#endif
}
private:
inline static State mState{State::Test0};
};
using fsm = FSM;
}
int main() {
while(true) {
const uint8_t b = r;
fsm::process(b);
}
}
and one as equivalent if-statments.
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#define MORE
//#define ENTER
volatile uint8_t r = 0;
volatile uint8_t x = 0;
namespace {
struct FSM {
enum class State : uint8_t {Test0, Test1, Test2, Test3, Test4
#ifdef MORE
, Test5, Test6, Test7
#endif
};
inline static void process(const uint8_t b) {
const auto oldState = mState;
if (mState == State::Test0) {
if (b == 0) {
mState = State::Test1;
}
}
else {
if (mState == State::Test1) {
if (b == 1) {
mState = State::Test2;
}
}
else {
if (mState == State::Test2) {
if (b == 2) {
mState = State::Test3;
}
}
else {
if (mState == State::Test3) {
if (b == 3) {
mState = State::Test4;
}
}
else {
if (mState == State::Test4) {
if (b == 4) {
#ifndef MORE
mState = State::Test0;
#else
mState = State::Test5;
}
}
else {
if (mState == State::Test5) {
if (b == 5) {
mState = State::Test6;
}
}
else {
if (mState == State::Test6) {
if (b == 6) {
mState = State::Test7;
}
}
else {
if (mState == State::Test7) {
if (b == 7) {
mState = State::Test0;
}
}
}
#endif
}
}
}
}
}
}
#ifdef ENTER
if (oldState != mState) {
if (mState == State::Test1) {
x = 41;
}
else {
if (mState == State::Test2) {
x = 42;
}
else {
if (mState == State::Test3) {
x = 43;
}
else {
if (mState == State::Test4) {
x = 44;
}
#ifdef MORE
else {
if (mState == State::Test5) {
x = 45;
}
else {
if (mState == State::Test6) {
x = 46;
}
else {
if (mState == State::Test7) {
x = 47;
}
}
}
}
#endif
}
}
}
}
#endif
}
private:
inline static State mState{State::Test0};
};
using fsm = FSM;
}
int main() {
while(true) {
const uint8_t b = r;
fsm::process(b);
}
}
https://godbolt.org/z/7TdEo31KE
Side note: this is a stripped down version of some tests I made to implement the finite-state-machine with template-metaprogramming, which is quite straight forward and fundamentally gives the same copy as the if-statement example by template-recursion.
First I found that gcc starts to generate a jump-table for the switch-implementation using more than 5 states, the same occurs in the if-statement example with more than 7 states.
But even with more than 7 states the code is fundamentally different: the if-statement example uses a 7-entry jump-table whereas the switch-statement example uses a jump-table with 8 entries. This is also true for x86 backend and also for clang.
My assembler skills are limited, therefore two questions arise:
- which one is more performant?
- can some changes to the code make the two version look the same in assembler?