embedding' a meta state machine

53
1 emBO++ 2017 'EMBEDDING' A META STATE MACHINE Kris Jusiak, Quantlab | | [email protected] @krisjusiak linkedin.com/in/kris-jusiak

Upload: emboconference

Post on 19-Mar-2017

697 views

Category:

Technology


5 download

TRANSCRIPT

Page 1: Embedding' a meta state machine

1

emBO++ 2017

'EMBEDDING' A METASTATE MACHINE

Kris Jusiak, Quantlab

| | [email protected] @krisjusiak linkedin.com/in/kris-jusiak

Page 2: Embedding' a meta state machine

1

2

AGENDAConnection example

ImplementationNaiveEnum/SwitchVariant (C++17)State Machine Language (SML)

[Boost].SMLOverviewSML vs Boost.MSM vs Boost.StatechartDesign in a nutshell

Summary

Page 3: Embedding' a meta state machine

3 . 1

STORY: CONNECTION (BDD STYLE)Scenario 1: Establishing connection Given the connection is disconnected When the user requests to connect Then the establish request should be sent And the system should wait for estaliblished event

Scenario 2: Keeping connection Given the connection is connected When the valid ping is recieved Then the timeout should be reset

Scenario 3: Connection timeout Given the connection is connected When the timeout is recieved Then the establish should be called And connecting process start again

Scenario 4: Disconnecting Given the connection is connected When the disconnect event is recieved Then the close should be called And user should get disconnected

Page 4: Embedding' a meta state machine

3 . 2

CONNECTION: STATE DIAGRAM (UML 2.5)

Transition - Uni ed Modeling Language (UML)

Page 5: Embedding' a meta state machine

3 . 3

CONNECTION - HELPERS/DETAILSvoid resetTimeout() std::puts("resetTimeout"); void establish() std::puts("establish"); void close() std::puts("close"); bool is_valid(const Ping&) return true;

Page 6: Embedding' a meta state machine

3 . 4

CONNECTION (V1) - NAIVE IMPLEMENTATIONclass ConnectionV1 bool disconnected = true, connected = false, connecting = false;

public: void connect() if (disconnected || connected) establish(); disconnected = false; connecting = true;

void disconnect() if (connecting || connected) close(); connected = false; disconnected = true;

Page 7: Embedding' a meta state machine

3 . 5

CONNECTION (V1) - NAIVE IMPLEMENTATION void established() connecting = false; connected = true;

void ping(const Ping& event) if (connected && is_valid(event)) resetTimeout();

void timeout() connect(); ;

Page 8: Embedding' a meta state machine

3 . 6

CONNECTION (V1) - BENCHMARKGCC-7 Clang-3.9

Compilation time 0.101s 0.122s

sizeof(ConnectionV1) 3b 3b

Executable size 6.2K 6.2K

Connect: ASM x86-64

main: sub rsp, 8 mov edi, OFFSET FLAT:.LC1 call puts xor eax, eax add rsp, 8 ret

https://godbolt.org/g/0qpgvv

Page 9: Embedding' a meta state machine

3 . 7

CONNECTION (V1) - SUMMARY(+) Quick compilation times(+) Good performance(-) Uses more size than required(-) Hard to follow(-) Hard to extend and maintain(-) Hard to test

Page 10: Embedding' a meta state machine

3 . 8

CONNECTION (V2) - ENUM/SWITCHIMPLEMENTATION

class ConnectionV2 enum class State : unsigned char DISCONNECTED, CONNECTING, CONNECTED state;

public: void connect() switch(state) default: break; case State::DISCONNECTED: case State::CONNECTED: establish(); state = State::CONNECTING; break;

void disconnect() switch(state) default: break; case State::CONNECTING: case State::CONNECTED: close(); state = State::DISCONNECTED; break;

Page 11: Embedding' a meta state machine

3 . 9

CONNECTION (V2) - ENUM/SWITCHIMPLEMENTATION

void established() state = State::CONNECTED;

void ping(const Ping& event) if (state == State::CONNECTED && is_valid(event)) resetTimeout();

void timeout() connect(); ;

Page 12: Embedding' a meta state machine

3 . 10

CONNECTION (V2) - BENCHMARKGCC-7 Clang-3.9

Compilation time 0.112s 0.118s

sizeof(ConnectionV2) 1b 1b

Executable size 6.2K 6.2K

Connect: ASM x86-64

main: sub rsp, 8 mov edi, OFFSET FLAT:.LC1 call puts xor eax, eax add rsp, 8 ret

https://godbolt.org/g/3Qphjb

Page 13: Embedding' a meta state machine

3 . 11

CONNECTION (V2) - SUMMARY(+) Quick compilation times(+) Good performance(+) Minimal size used(-) Hard to follow(-) Hard to extend and maintain(-) Hard to test

Page 14: Embedding' a meta state machine

3 . 12

CONNECTION (V3) - C++17 VARIANTIMPLEMENTATION

class ConnectionV3 struct Disconnected ; struct Connecting ; struct Connected ;

std::variant<Disconnected, Connecting, Connected> state;

public: void connect() std::visit(overload [&](Disconnected) establish(); state = Connecting; , [&](Connected) establish(); state = Connecting; , [](auto) , state);

Page 15: Embedding' a meta state machine

3 . 13

CONNECTION (V3) - C++17 VARIANTIMPLEMENTATION

void disconnect() std::visit(overload [&](Connecting) close(); state = Disconnected; , [&](Connected) close(); state = Disconnected; , [](auto) , state);

void established() state = Connected;

void ping(const Ping& event) if (std::get_if<Connected>(&state) && is_valid(event)) resetTimeout();

void timeout() connect(); ;

Page 16: Embedding' a meta state machine

3 . 14

CONNECTION (V3) - BENCHMARKGCC-7 Clang-3.9

Compilation time 0.272s 0.269s

sizeof(ConnectionV3) 2b 8b

Executable size 6.2K 6.2K

Connect: ASM x86-64

main: sub rsp, 56 xor eax, eax lea rsi, [rsp+16] lea rdi, [rsp+32] mov WORD PTR [rsp+16], ax mov QWORD PTR [rsp+8], rsi movq xmm0, QWORD PTR [rsp+8] ...

https://godbolt.org/g/yf1BjN

Page 17: Embedding' a meta state machine

3 . 15

CONNECTION (V3) - SUMMARY(+) Data connected with the state naturally(+) Quick compilation times(-) Poor performance(-) Uses more size than required (different on GCC andClang)(-) Hard to follow(-) Hard to extend and maintain(-) Hard to test

Page 18: Embedding' a meta state machine

3 . 16

LET'S GO BACK TO THE CONNECTION STATEDIAGRAM (UML 2.5)

Page 19: Embedding' a meta state machine

3 . 17

CONNECTION: TRANSITION TABLEREPRESENTATION (UML 2.5)

Page 20: Embedding' a meta state machine

3 . 18

CONNECTION: TRANSITION TABLE TEXTREPRESENTATION (UML 2.5)

* ­> Disconnected : connect / establish ­> Connecting

Connecting : established ­> Connected

Connected : ping [ is_valid ] / resetTimeout

Connected : timeout ­> Connecting

Connected : disconnect ­> Disconnected

Page 21: Embedding' a meta state machine

3 . 19

LET'S INTRODUCE [BOOST].SMLCONNECTION (V4)

sml::sm connectionV4 = [] // C++17 template arg. deduction for class templates return transition_table * "Disconnected"_s + connect / establish = "Connecting"_s, "Connecting"_s + established = "Connected"_s, "Connected"_s + ping [ is_valid ] / resetTimeout, "Connected"_s + timeout / establish = "Connecting"_s, "Connected"_s + disconnect / close = "Disconnected"_s ; ;

Page 22: Embedding' a meta state machine

3 . 20

CONNECTION (V4) - [BOOST].SMLACTIONS

const auto establish = [] std::puts("establish!"); ; const auto disconnect = [] std::puts("disconnect!"); ;

GUARDSconst auto is_valid = [](auto event) return true; ;

Page 23: Embedding' a meta state machine

3 . 21

CONNECTION (V4) - BENCHMARKGCC-7 Clang-3.9

Compilation time 0.151s 0.169s

sizeof(ConnectionV4) 1b 1b

Executable size 6.2K 6.2K

Connect: ASM x86-64

main: subq $8, %rsp movl $.LC0, %edi call puts xorl %eax, %eax addq $8, %rsp ret

https://godbolt.org/g/6z6UF4

Page 24: Embedding' a meta state machine

3 . 22

CONNECTION (V4) - SUMMARY(+) Quick compilation times(+) Good performance(+) Minimal size used(+) Easy to follow(+) Easty to extend and maintain(+) Easy to test(-) Library has to be used (C++14)

Page 25: Embedding' a meta state machine

3 . 23

CONNECTION - BENCHMARKNaive Enum/Switch Variant [Boost].SML

Compilationtime

0.101s 0.112s 0.269s 0.151s

sizeof(sm) 3b 1b 2b/8b 1b

Executablesize

6.2K 6.2K 6.2K 6.2K

Performance(connect)

inlined inlined not-inlined

inlined

Page 26: Embedding' a meta state machine

3 . 24

MORE REALIST BENCHMARK

Events States Transitions Process Events

50 50 50 1'000'000

Page 27: Embedding' a meta state machine

3 . 25

MORE REALIST BENCHMARKMAIN

int main() for (auto i = 0; i < 1'000'000; ++i) if (rand() % 2) sm.process_event(e1()); if (rand() % 2) sm.process_event(e2()); if (rand() % 2) sm.process_event(e3()); ... if (rand() % 2) sm.process_event(e100());

CXXFLAGS$CXX ­std=c++1z ­O3 ­flto ­fno­exceptions ­DNDEBUG benchmark.cpp

Page 28: Embedding' a meta state machine

3 . 26

MORE REALIST - RESULTSBENCHMARKEnum/Switch Variant [Boost].SML

Compilationtime

0.132s 15.321s 0.582s

Execution time 679ms 827ms 622ms

Memory usage 1b 2b/8b 1b

Executable size 15K 187K 34K

Line of Code(LOC)

~300 (nomacros)

~300 ~50

Median / 100 runs

Page 29: Embedding' a meta state machine

4 . 1

[BOOST].SMLUML-2.5 State Machine Language

https://github.com/boost-experimental/sml

Page 30: Embedding' a meta state machine

4 . 2

[BOOST].SMLOne header - 2k LOC - (boost/sml.hpp) / generatedNeither Boost nor STL is requiredQuick compilation-times (-Wall -Wextra -Werror -pedantic-pedantic-errors)Blazing fast run-time (Generated at compile-time)No 'virtual's (-fno-rtti)Optional support for 'exception's (-fno-exceptions)Supported compilers (C++14)

, , , Clang-3.4+ XCode-6.1+ GCC-5.2+ MSVC-2015+

Page 31: Embedding' a meta state machine

4 . 3

- API (SIMPLIFIED)[BOOST].SML/** * Makes transition table from DSL * @tparam Ts... transitions (transitional) */ template <class... Ts> requires transitional<Ts>()... struct transition_table;

/** * Helper function to make transition_table (C++14) * @tparam Ts... transitions (transitional) * @return transition_table */ template <class... Ts> requires transitional<Ts>()... constexpr auto make_transition_table(Ts&&...) noexcept;

Page 32: Embedding' a meta state machine

4 . 4

- API (SIMPLIFIED)[BOOST].SML/** * State Machine * * @tparam T Callable object returning transition_table * @tparam TPolicies policies to be applied such as * thread safe, exception safe, etc. */ template<class T, class... TPolicies> requires callable<transition_table, T>() class sm;

/** * Process event * Complexity ­ O(1) * @param TEvent event to be processed */ template<class TEvent> constexpr void process_event(const TEvent&);

Page 33: Embedding' a meta state machine

4 . 5

- FEATURES[BOOST].SMLUML (2.5)

TransitionAnonymous, Internal, Self, Local transitionGuard, Action

Unexpected, Deffered, Any eventState

Entry/Exit Actions, Initial, Terminate stateComposite/Sub state

Explicit entry/exit, ForkShallow History, Deep History

Orthogonal regionsNon-UML

Logging, State visitor, Diagram gen., Run-time dispatch

Page 34: Embedding' a meta state machine

4 . 6

- FEATURES[BOOST].SMLWe DON'T pay for features we are NOT using!

For example, if the state machine doesn't useorthogonal regions, the code responsible for

handling them won't be even generated

Page 35: Embedding' a meta state machine

- MORE REALISTIC EXAMPLE (SYSTEM)[BOOST].SML

Page 36: Embedding' a meta state machine

4 . 7

Enum/Switch? No, thank you!

Page 37: Embedding' a meta state machine

4 . 8

- MORE REALISTIC EXAMPLE (SYSTEM)[BOOST].SMLstruct Connection auto operator()() const

return transition_table "Disconnected"_s(H) + connect / []establish(); = "Connecting"_s, "Connecting"_s + established = "Connected"_s, "Connected"_s + ping [ is_valid ] / []resetTimeout(); "Connected"_s + timeout / []establish(); = "Connecting"_s, "Connected"_s + disconnect / []close(); = "Disconnected"_s ; ;

struct System auto operator()() const

return transition_table * "Idle"_s + power_up [ has_battery and is_healthy] / connect = state<Connection>, state<Connection> + suspend = "Suspended"_s, "Suspended"_s + resume = state<Connection>, // ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ // * "Watchdog"_s + tick / resetTimeout "Watchdog"_s + timeout = X ;

Page 38: Embedding' a meta state machine

4 . 9

- MORE REALISTIC EXAMPLE (SYSTEM)[BOOST].SMLint main() using namespace sml; sm<System> system;

system.process_event(power_up); assert(system.is(state<Connection>, "Watchdog"_s));

system.process_event(suspend); assert(system.is("Suspended"_s, "Watchdog"_s));

system.process_event(timeout); assert(system.is(X)); // true if any state is in terminated state (X)

Page 39: Embedding' a meta state machine

4 . 10

[BOOST].SML VS BOOST.MSM-EUML VSBOOST.STATECHART

Page 40: Embedding' a meta state machine

4 . 11

OVERVIEWLibrary [Boost].SML Boost.MSM-

eUMLBoost.Statechart

Standard C++14 C++98/03 C++98/03

Version 1.0.1 1.63 1.63

License Boost 1.0 Boost 1.0 Boost 1.0

Linkage header only header only header only

Page 41: Embedding' a meta state machine

4 . 12

(SAME AS BEFORE)BENCHMARK

Events States Transitions Process Events

50 50 50 1'000'000

Page 42: Embedding' a meta state machine

4 . 13

- RESULTSBENCHMARK[Boost].SML Boost.MSM-

eUMLBoost.Statechart

Compilationtime

0.582s 1m15.935s 5.671s

Executiontime

622ms 664ms 2282ms

Memoryusage

1b 120b 224b

Executablesize

34K 611K 211K

Page 43: Embedding' a meta state machine

5 . 1

DESIGN IN A NUTSHELL[BOOST].SML

Page 44: Embedding' a meta state machine

5 . 2

TRANSITIONAL CONCEPTtemplate <class T> concept bool transitional() return requires(T transition) typename T::src_state; typename T::dst_state; typename T::event; T::property; // initial, history state, etc.

transition.execute(const typename T::Event&) ­> optional<state_t>; ;

Page 45: Embedding' a meta state machine

5 . 3

FRONT ENDDOMAIN SPECIFIC LANGUAGE (DSL)

static_assert(std::is_same< decltype(

transition_table * "Disconnected"_s + connect / establish = "Connecting"_s, "Connecting"_s + established = "Connected"_s ),

transition_table< // ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ // // src_state dst_state event guard action // // ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­ // transition<state<"Disconnected">, state<"Connecting">, connect, always, establish>, transition<state<"Connecting">, state<"Connected">, established, always, none> > >);

Page 46: Embedding' a meta state machine

5 . 4

BACK-ENDGENERATED AT COMPILE TIME MAPPING PER EVENT AND STATE

using mappings_t = type_list< pair<connect, type_list< transitions< transition<state<"Disconnected">, state<"Connecting">, connect, always, establish> >, transitions<>, // Connecting (unexpected event) transitions<> // Connected (unexpected event) >, pair<established, type_list< transitions<>, // Disconnected (unexpected event) transitions< transition<state<"Connecting">, state<"Connected">, established, always, none> >, transitions<> // Connected (unexpected event) >

Page 47: Embedding' a meta state machine

5 . 5

BACK-ENDPROCESS EVENT - JUMP TABLE - (SIMPLIFIED)

template<class TEvent> constexpr void process_event(const TEvent& event) process_event_impl(event, get_mappings_t<TEvent>);

template<class... Transitions, class TEvent> constexpr void process_event_impl(const TEvent& event, type_list<Transitions...>) const static (*dispatch_table[])(const TEvent&) = &Transitions::template execute<TEvent>... ; dispatch_table[current_state](event); // Complexity: O(1) ­ jump table

Page 48: Embedding' a meta state machine

6 . 1

SUMMARY

Page 49: Embedding' a meta state machine

6 . 2

IF YOU LIKE IT OR NOT, YOUR CODE WON'T BESTATEFUL (MOST LIKELY)

Page 50: Embedding' a meta state machine

6 . 3

IMPLICIT/HAND WRITTEN STATE MACHINESARE HARD TOReason aboutMaintain / ExtendTest

Page 51: Embedding' a meta state machine

6 . 4

STATE MACHINES ARE MORE THAN JUST SIMPLETRANSITIONS

UML-2.5

Page 52: Embedding' a meta state machine

6 . 5

LEVERAGING ZERO-COST LIBRARIES WILL BOOSTYOUR DESIGN AND/OR PERFRORMANCE

/ [Boost].SML Boost.MSM

Page 53: Embedding' a meta state machine

7

QUESTIONS?[Boost].SML

Documentation

Source Code

Try it online!

http://boost-experimental.github.io/sml

https://github.com/boost-experimental/sml

http://boost-experimental.github.io/sml/examples

https://godbolt.org/g/UX6eWt

-

| | [email protected] @krisjusiak linkedin.com/in/kris-jusiak