Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

'Embedding' a meta state machine

4,848 views

Published on

by Kris Jusiak

Published in: Technology
  • Be the first to comment

  • Be the first to like this

'Embedding' a meta state machine

  1. 1. emBO++ 2017 'EMBEDDING' A META STATE MACHINE Kris Jusiak, Quantlab | |kris@jusiak.net @krisjusiak linkedin.com/in/kris-jusiak
  2. 2. 1 2 AGENDA Connection example Implementation Naive Enum/Switch Variant (C++17) State Machine Language (SML) [Boost].SML Overview SML vs Boost.MSM vs Boost.Statechart Design in a nutshell Summary
  3. 3. 3 . 1 STORY: CONNECTION (BDD STYLE) enario 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  enario 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 recieve   Then the close should be called    And user should get disconnected 
  4. 4. 3 . 2 CONNECTION: STATE DIAGRAM (UML 2.5) Transition - Uni ed Modeling Language (UML)
  5. 5. 3 . 3 CONNECTION - HELPERS/DETAILS void resetTimeout() { std::puts("resetTimeout"); }  void establish() { std::puts("establish"); }  void close() { std::puts("close"); }  bool is_valid(const Ping&) { return true; } 
  6. 6. 3 . 4 CONNECTION (V1) - NAIVE IMPLEMENTATION class 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;          }      } 
  7. 7. 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(); }  }; 
  8. 8. 3 . 6 CONNECTION (V1) - BENCHMARK GCC-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
  9. 9. 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
  10. 10. 3 . 8 CONNECTION (V2) - ENUM/SWITCH IMPLEMENTATION 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;          }      } 
  11. 11. 3 . 9 CONNECTION (V2) - ENUM/SWITCH IMPLEMENTATION     void established() {         state = State::CONNECTED;      }      void ping(const Ping& event) {          if (state == State::CONNECTED && is_valid(event)) {              resetTimeout();          }      }      void timeout() { connect(); }  }; 
  12. 12. 3 . 10 CONNECTION (V2) - BENCHMARK GCC-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
  13. 13. 3 . 11 CONNECTION (V2) - SUMMARY (+) Quick compilation times (+) Good performance (+) Minimal size used (-) Hard to follow (-) Hard to extend and maintain (-) Hard to test
  14. 14. 3 . 12 CONNECTION (V3) - C++17 VARIANT IMPLEMENTATION 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);      } 
  15. 15. 3 . 13 CONNECTION (V3) - C++17 VARIANT IMPLEMENTATION     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(); }  }; 
  16. 16. 3 . 14 CONNECTION (V3) - BENCHMARK GCC-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
  17. 17. 3 . 15 CONNECTION (V3) - SUMMARY (+) Data connected with the state naturally (+) Quick compilation times (-) Poor performance (-) Uses more size than required (different on GCC and Clang) (-) Hard to follow (-) Hard to extend and maintain (-) Hard to test
  18. 18. 3 . 16 LET'S GO BACK TO THE CONNECTION STATE DIAGRAM (UML 2.5)
  19. 19. 3 . 17 CONNECTION: TRANSITION TABLE REPRESENTATION (UML 2.5)
  20. 20. 3 . 18 CONNECTION: TRANSITION TABLE TEXT REPRESENTATION (UML 2.5) * ­> Disconnected : connect / establish              ­> Connecting       Connecting   : established                      ­> Connected       Connected    : ping [ is_valid ] / resetTimeout       Connected    : timeout                          ­> Connecting       Connected    : disconnect                       ­> Disconnected 
  21. 21. 3 . 19 LET'S INTRODUCE [BOOST].SML CONNECTION (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    };  }; 
  22. 22. 3 . 20 CONNECTION (V4) - [BOOST].SML ACTIONS const auto establish = []{ std::puts("establish!"); };  const auto disconnect = []{ std::puts("disconnect!"); };  GUARDS const auto is_valid = [](auto event){ return true; }; 
  23. 23. 3 . 21 CONNECTION (V4) - BENCHMARK GCC-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
  24. 24. 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)
  25. 25. 3 . 23 CONNECTION - BENCHMARK Naive Enum/Switch Variant [Boost].SML Compilation time 0.101s 0.112s 0.269s 0.151s sizeof(sm) 3b 1b 2b/8b 1b Executable size 6.2K 6.2K 6.2K 6.2K Performance (connect) inlined inlined not- inlined inlined
  26. 26. 3 . 24 MORE REALIST BENCHMARK Events States Transitions Process Events 50 50 50 1'000'000
  27. 27. 3 . 25 MORE REALIST BENCHMARK MAIN 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 
  28. 28. 3 . 26 MORE REALIST - RESULTSBENCHMARK Enum/Switch Variant [Boost].SML Compilation time 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 (no macros) ~300 ~50 Median / 100 runs
  29. 29. 4 . 1 [BOOST].SML UML-2.5 State Machine Language https://github.com/boost-experimental/sml
  30. 30. 4 . 2 [BOOST].SML One header - 2k LOC - (boost/sml.hpp) / generated Neither Boost nor STL is required Quick 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+
  31. 31. 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; 
  32. 32. 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&); 
  33. 33. 4 . 5 - FEATURES[BOOST].SML UML (2.5) Transition Anonymous, Internal, Self, Local transition Guard, Action Unexpected, Deffered, Any event State Entry/Exit Actions, Initial, Terminate state Composite/Sub state Explicit entry/exit, Fork Shallow History, Deep History Orthogonal regions Non-UML Logging, State visitor, Diagram gen., Run-time dispatch
  34. 34. 4 . 6 - FEATURES[BOOST].SML We DON'T pay for features we are NOT using! For example, if the state machine doesn't use orthogonal regions, the code responsible for handling them won't be even generated
  35. 35. - MORE REALISTIC EXAMPLE (SYSTEM)[BOOST].SML
  36. 36. 4 . 7 Enum/Switch? No, thank you!
  37. 37. 4 . 8 - MORE REALISTIC EXAMPLE (SYSTEM)[BOOST].SML uct Connection {  uto 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   };  ;  uct System {  uto operator()() const {   return transition_table{    * "Idle"_s          + power_up [ has_battery and is_healthy] / connect = state<Connectio     state<Connection> + suspend                                          = "Suspended"_s,      "Suspended"_s     + resume                                           = state<Connectio   // ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­    * "Watchdog"_s      + tick / resetTimeout      "Watchdog"_s      + timeout                                          = X   }; 
  38. 38. 4 . 9 - MORE REALISTIC EXAMPLE (SYSTEM)[BOOST].SML int 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)  } 
  39. 39. 4 . 10 [BOOST].SML VS BOOST.MSM-EUML VS BOOST.STATECHART
  40. 40. 4 . 11 OVERVIEW Library [Boost].SML Boost.MSM- eUML Boost.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
  41. 41. 4 . 12 (SAME AS BEFORE)BENCHMARK Events States Transitions Process Events 50 50 50 1'000'000
  42. 42. 4 . 13 - RESULTSBENCHMARK [Boost].SML Boost.MSM- eUML Boost.Statechart Compilation time 0.582s 1m15.935s 5.671s Execution time 622ms 664ms 2282ms Memory usage 1b 120b 224b Executable size 34K 611K 211K
  43. 43. 5 . 1 DESIGN IN A NUTSHELL[BOOST].SML
  44. 44. 5 . 2 TRANSITIONAL CONCEPT template <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>;    }  }; 
  45. 45. 5 . 3 FRONT END DOMAIN SPECIFIC LANGUAGE (DSL) atic_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>  >  }); 
  46. 46. 5 . 4 BACK-END GENERATED AT COMPILE TIME MAPPING PER EVENT AND STATE ing 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)  > 
  47. 47. 5 . 5 BACK-END PROCESS 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  } 
  48. 48. 6 . 1 SUMMARY
  49. 49. 6 . 2 IF YOU LIKE IT OR NOT, YOUR CODE WON'T BE STATEFUL (MOST LIKELY)
  50. 50. 6 . 3 IMPLICIT/HAND WRITTEN STATE MACHINES ARE HARD TO Reason about Maintain / Extend Test
  51. 51. 6 . 4 STATE MACHINES ARE MORE THAN JUST SIMPLE TRANSITIONS UML-2.5
  52. 52. 6 . 5 LEVERAGING ZERO-COST LIBRARIES WILL BOOST YOUR DESIGN AND/OR PERFRORMANCE /[Boost].SML Boost.MSM
  53. 53. 7 QUESTIONS? ost].SML cumentation urce Code 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 - | |kris@jusiak.net @krisjusiak linkedin.com/in/kris-jusiak

×