Parcourir la source

When not debugging, use 'int' type instead of our Bitmap type (which inherits 'int'). Almost halves transition execution time!

Joeri Exelmans il y a 5 ans
Parent
commit
26474302b9

+ 2 - 2
src/sccd/cd/cd.py

@@ -40,10 +40,10 @@ class SingleInstanceCD(AbstractCD):
           print("  %s" % event_name)
     print()
     print("Internal events:")
-    for event_id in self.statechart.internal_events.items():
+    for event_id in bm_items(self.statechart.internal_events):
       print("  %s" % self.globals.events.get_name(event_id))
     print()
     print("All event triggers:")
-    for event_id in self.statechart.events.items():
+    for event_id in bm_items(self.statechart.events):
       print("  %s" % self.globals.events.get_name(event_id))
     print()

+ 1 - 1
src/sccd/statechart/dynamic/memory_snapshot.py

@@ -39,7 +39,7 @@ class MemoryPartialSnapshot(MemoryInterface):
     # Sometimes read from snapshot
     if frame is self.frame:
       # "our" frame! :)
-      if self.trans_dirty.has(offset):
+      if bm_has(self.trans_dirty, offset):
         return self.actual[offset]
       else:
         return self.snapshot[offset]

+ 2 - 2
src/sccd/statechart/dynamic/round.py

@@ -60,7 +60,7 @@ class CandidatesGeneratorEventBased(CandidatesGenerator):
 
     def __post_init__(self):
         # Prepare cache with all single-item sets-of-events since these are the most common sets of events.
-        for event_id in self.statechart.events.items():
+        for event_id in bm_items(self.statechart.events):
             events_bitmap = bit(event_id)
             self.cache[(events_bitmap, 0)] = self._candidates(events_bitmap, 0)
 
@@ -73,7 +73,7 @@ class CandidatesGeneratorEventBased(CandidatesGenerator):
         return candidates
 
     def generate(self, state, enabled_events: List[Event], forbidden_arenas: Bitmap) -> Iterable[Transition]:
-        events_bitmap = Bitmap.from_list(e.id for e in enabled_events)
+        events_bitmap = bm_from_list(e.id for e in enabled_events)
         key = (events_bitmap, forbidden_arenas)
 
         try:

+ 12 - 11
src/sccd/statechart/dynamic/statechart_execution.py

@@ -22,8 +22,8 @@ class StatechartExecution:
         # set of current states
         self.configuration: Bitmap = Bitmap()
 
-        # mapping from history state id to states to enter if history is target of transition
-        self.history_values: Dict[int, Bitmap] = {}
+        # mapping from history_id to set of states to enter if history is target of transition
+        self.history_values: List[Bitmap] = {}
 
         # For each AfterTrigger in the statechart tree, we keep an expected 'id' that is
         # a parameter to a future 'after' event. This 'id' is incremented each time a timer
@@ -35,14 +35,14 @@ class StatechartExecution:
 
     # enter default states
     def initialize(self):
-        states = self.statechart.tree.root.target_states(self)
+        states = self.statechart.tree.root.opt.ts_static
         self.configuration = states
 
         ctx = EvalContext(current_state=self, events=[], memory=self.rhs_memory)
         if self.statechart.datamodel is not None:
             self.statechart.datamodel.exec(self.rhs_memory)
 
-        for state in self._ids_to_states(states.items()):
+        for state in self._ids_to_states(bm_items(states)):
             print_debug(termcolor.colored('  ENTER %s'%state.opt.full_name, 'green'))
             self._perform_actions(ctx, state.enter)
             self._start_timers(state.opt.after_triggers)
@@ -60,17 +60,19 @@ class StatechartExecution:
             # print("arena is:", t.opt.arena)
             timer.start("transition")
 
-            timer.start("exit set")
             # Sequence of exit states is the intersection between set of current states and the arena's descendants.
+            timer.start("exit set")
             exit_ids = self.configuration & t.opt.arena.opt.descendants
-            exit_set = self._ids_to_states(exit_ids.reverse_items())
             timer.stop("exit set")
+            exit_set = self._ids_to_states(bm_reverse_items(exit_ids))
 
 
             timer.start("enter set")
             # Sequence of enter states is more complex but has for a large part already been computed statically.
-            enter_ids = t.opt.enter_states_static | reduce(lambda x,y: x|y, (s.target_states(self) for s in t.opt.enter_states_dynamic), Bitmap())
-            enter_set = self._ids_to_states(enter_ids.items())
+            if t.opt.enter_states_dynamic:
+                print(t.opt.enter_states_dynamic)
+            enter_ids = t.opt.enter_states_static | reduce(lambda x,y: x|y, (self.history_values[s.history_id] for s in t.opt.enter_states_dynamic), Bitmap())
+            enter_set = self._ids_to_states(bm_items(enter_ids))
             timer.stop("enter set")
 
             ctx = EvalContext(current_state=self, events=events, memory=self.rhs_memory)
@@ -83,8 +85,7 @@ class StatechartExecution:
                 print_debug(termcolor.colored('  EXIT %s' % s.opt.full_name, 'green'))
                 # remember which state(s) we were in if a history state is present
                 for h, mask in s.opt.history:
-                    self.history_values[h.opt.state_id] = exit_ids & mask
-                    # print(list(self._ids_to_states(self.history_values[h.opt.state_id].items())))
+                    self.history_values[h.history_id] = exit_ids & mask
                 self._perform_actions(ctx, s.exit)
                 self.configuration &= ~s.opt.state_id_bitmap
             timer.stop("exit states")
@@ -162,7 +163,7 @@ class StatechartExecution:
     # Return whether the current configuration includes ALL the states given.
     def in_state(self, state_strings: List[str]) -> bool:
         state_ids_bitmap = states_to_bitmap((self.statechart.tree.state_dict[state_string] for state_string in state_strings))
-        in_state = self.configuration.has_all(state_ids_bitmap)
+        in_state = bm_has_all(self.configuration, state_ids_bitmap)
         # if in_state:
         #     print_debug("in state"+str(state_strings))
         # else:

+ 19 - 21
src/sccd/statechart/static/tree.py

@@ -27,29 +27,22 @@ class State:
         if self.parent is not None:
             self.parent.children.append(self)
 
-    def target_states(self, instance) -> Bitmap:
-        return self.opt.ts_static
-
-    def static_additional_target_states(self, exclude: 'State') -> Tuple[Bitmap, List['HistoryState']]:
+    def _static_additional_target_states(self, exclude: 'State') -> Tuple[Bitmap, List['HistoryState']]:
         return (self.opt.state_id_bitmap, [])
 
     def __repr__(self):
         return "State(\"%s\")" % (self.short_name)
 
+@dataclass
 class HistoryState(State):
+    history_id: Optional[int] = None
+
     # Set of states that may be history values.
     @abstractmethod
     def history_mask(self) -> Bitmap:
         pass
 
-    def target_states(self, instance) -> Bitmap:
-        try:
-            return instance.history_values[self.opt.state_id]
-        except KeyError:
-            # Parent's target states, but not the parent itself:
-            return self.parent.target_states(instance) & ~self.parent.opt.state_id_bitmap
-
-    def static_additional_target_states(self, exclude: 'State') -> Tuple[Bitmap, List['HistoryState']]:
+    def _static_additional_target_states(self, exclude: 'State') -> Tuple[Bitmap, List['HistoryState']]:
         assert False # history state cannot have children and therefore should never occur in a "enter path"
 
 class ShallowHistoryState(HistoryState):
@@ -72,7 +65,7 @@ class DeepHistoryState(HistoryState):
 
 class ParallelState(State):
 
-    def static_additional_target_states(self, exclude: 'State') -> Tuple[Bitmap, List['HistoryState']]:
+    def _static_additional_target_states(self, exclude: 'State') -> Tuple[Bitmap, List['HistoryState']]:
         return (self.opt.ts_static & ~exclude.opt.ts_static, [s for s in self.opt.ts_dynamic if s not in exclude.opt.ts_dynamic])
 
     def __repr__(self):
@@ -98,7 +91,7 @@ class Trigger:
         # Optimization: Require 'enabling' to be sorted!
         assert sorted(self.enabling, key=lambda e: e.id) == self.enabling
 
-        self.enabling_bitmap = Bitmap.from_list(e.id for e in self.enabling)
+        self.enabling_bitmap = bm_from_list(e.id for e in self.enabling)
 
     def check(self, events_bitmap: Bitmap) -> bool:
         return (self.enabling_bitmap & events_bitmap) == self.enabling_bitmap
@@ -131,7 +124,7 @@ class NegatedTrigger(Trigger):
 
     def __post_init__(self):
         Trigger.__post_init__(self)
-        self.disabling_bitmap = Bitmap.from_list(e.id for e in self.disabling)
+        self.disabling_bitmap = bm_from_list(e.id for e in self.disabling)
 
     def check(self, events_bitmap: Bitmap) -> bool:
         return Trigger.check(self, events_bitmap) and not (self.disabling_bitmap & events_bitmap)
@@ -195,7 +188,6 @@ class StateOptimization:
     ts_static: Bitmap = Bitmap() 
     # Subset of descendants that are history states AND are in the subtree of states automatically entered if this state is the target of a transition.
     ts_dynamic: List[HistoryState] = field(default_factory=list)
-    # ts_dynamic: Bitmap = Bitmap()
 
     # Triggers of outgoing transitions that are AfterTrigger.
     after_triggers: List[AfterTrigger] = field(default_factory=list)
@@ -218,7 +210,7 @@ class StateTree:
     state_dict: Dict[str, State] # mapping from 'full name' to State
     after_triggers: List[AfterTrigger] # all after-triggers in the statechart
     stable_bitmap: Bitmap # set of states that are syntactically marked 'stable'
-
+    history_states: List[HistoryState] # all the history states in the statechart
 
 # Reduce a list of states to a set of states, as a bitmap
 def states_to_bitmap(state_list: List[State]) -> Bitmap:
@@ -229,6 +221,7 @@ def optimize_tree(root: State) -> StateTree:
 
     transition_list = []
     after_triggers = []
+    history_states = []
     def init_opt():
         next_id = 0
         def f(state: State, _=None):
@@ -244,6 +237,11 @@ def optimize_tree(root: State) -> StateTree:
                 if t.trigger and isinstance(t.trigger, AfterTrigger):
                     state.opt.after_triggers.append(t.trigger)
                     after_triggers.append(t.trigger)
+
+            if isinstance(state, HistoryState):
+                state.history_id = len(history_states)
+                history_states.append(state)
+
         return f
 
     def assign_full_name(state: State, parent_full_name: str = ""):
@@ -312,7 +310,7 @@ def optimize_tree(root: State) -> StateTree:
     for t in transition_list:
         # Arena can be computed statically. First computer Lowest-common ancestor:
         # Intersection between source & target ancestors, last member in depth-first sorted state list.
-        lca_id = (t.source.opt.ancestors & t.targets[0].opt.ancestors).highest_bit()
+        lca_id = bm_highest_bit(t.source.opt.ancestors & t.targets[0].opt.ancestors)
         lca = state_list[lca_id]
         arena = lca
         # Arena must be an Or-state:
@@ -329,7 +327,7 @@ def optimize_tree(root: State) -> StateTree:
         #   2) the arena's descendants
         enter_path = (t.targets[0].opt.state_id_bitmap | t.targets[0].opt.ancestors) & arena.opt.descendants
         # All states on the enter path will be entered, but on the enter path, there may also be AND-states whose children are not on the enter path, but should also be entered.
-        enter_path_iter = enter_path.items()
+        enter_path_iter = bm_items(enter_path)
         state_id = next(enter_path_iter, None)
         enter_states_static = Bitmap()
         enter_states_dynamic = []
@@ -339,7 +337,7 @@ def optimize_tree(root: State) -> StateTree:
             if next_state_id:
                 # an intermediate state on the path from arena to target
                 next_state = state_list[next_state_id]
-                static, dynamic = state.static_additional_target_states(next_state)
+                static, dynamic = state._static_additional_target_states(next_state)
                 enter_states_static |= static
                 enter_states_dynamic += dynamic
             else:
@@ -357,4 +355,4 @@ def optimize_tree(root: State) -> StateTree:
 
     timer.stop("optimize tree")
 
-    return StateTree(root, transition_list, state_list, state_dict, after_triggers, stable_bitmap)
+    return StateTree(root, transition_list, state_list, state_dict, after_triggers, stable_bitmap, history_states)

+ 81 - 75
src/sccd/util/bitmap.py

@@ -1,82 +1,88 @@
 from typing import *
 from functools import reduce
+from sccd.util.debug import *
+
+if DEBUG:
+  # Bitmap inherits 'int' and is therefore immutable.
+  # Methods that return a Bitmap return a new bitmap and leave the arguments untouched.
+  # To change a bitmap, use an assignment operator ('=', '|=', '&=', ...)
+  class Bitmap(int):
+    # Create from int
+    def __new__(cls, value=0, *args, **kwargs):
+      return super(cls, cls).__new__(cls, value)
+
+    def __repr__(self):
+      return "Bitmap('"+format(self, 'b')+"')"
+
+    def __str__(self):
+      return self.__repr__()
+
+    def __or__(self, other):
+      return self.__class__(super().__or__(other))
+
+    def __and__(self, other):
+      return self.__class__(super().__and__(other))
+
+    def __invert__(self):
+      return self.__class__(super().__invert__())
+
+    def __neg__(self):
+      return self.__class__(super().__neg__())
+else:
+  Bitmap = int # Much faster than our overrides! Only thing we miss out on is pretty printing as a binary string
 
-# Bitmap inherits 'int' and is therefore immutable.
-# Methods that return a Bitmap return a new bitmap and leave the arguments untouched.
-# To change a bitmap, use an assignment operator ('=', '|=', '&=', ...)
-class Bitmap(int):
-  # Create from int
-  def __new__(cls, value=0, *args, **kwargs):
-    return super(cls, cls).__new__(cls, value)
-
-  # iterable: positions of bits to set.
-  @classmethod
-  def from_list(cls, iterable):
-    v = reduce(lambda x,y: x|1<<y, iterable, 0) # iterable
-    return super(cls, cls).__new__(cls, v)
-
-  def __repr__(self):
-    return "Bitmap('"+format(self, 'b')+"')"
-
-  def __str__(self):
-    return self.__repr__()
-
-  def __or__(self, other):
-    return self.__class__(super().__or__(other))
-
-  def __and__(self, other):
-    return self.__class__(super().__and__(other))
-
-  def __invert__(self):
-    return self.__class__(super().__invert__())
-
-  def __neg__(self):
-    return self.__class__(super().__neg__())
-
-  def has(self, pos):
-    return self & 1 << pos
-
-  def has_all(self, bitmap):
-    return (self | bitmap) == self
-
-  def lowest_bit(self) -> int:
-    low = self & -self # only the lowest bit set
-    pos = -1
-    while low:
-      low >>= 1
-      pos += 1
-    return pos
-
-  def highest_bit(self) -> int:
-    pos = -1
-    while self:
-      self >>= 1
-      pos += 1
-    return pos
-
-  # Takes 1 iteration over our bitmap
-  def items(self) -> Iterable[int]:
-    pos = 0
-    while self > 0:
-      if (self >> 1) << 1 != self:
-        yield pos
-      pos += 1
-      self >>= 1
-
-  # Takes 2 iterations over our bitmap, one for highest_bit,
-  # and then to find the rest of the bits.
-  def reverse_items(self) -> Iterable[int]:
-    pos = self.highest_bit()
-    if pos >= 0:
-      yield pos
-    pos -= 1
-    while pos >= 0:
-      high = 1 << pos
-      if self & high:
-        yield pos
-      self -= high # unset high bit
-      pos -= 1
 
 # Create a bitmap with a single bit set.
 def bit(pos):
   return Bitmap(1 << pos)
+
+
+# The following functions are not methods of Bitmap so that they also work on integers:
+
+# Non-member function variants so they also work on integers:
+def bm_from_list(iterable):
+  v = reduce(lambda x,y: x|1<<y, iterable, 0) # iterable
+  return Bitmap(v)
+
+def bm_has(self, pos):
+  return self & 1 << pos
+
+def bm_has_all(self, bitmap):
+  return (self | bitmap) == self
+
+def bm_lowest_bit(self) -> int:
+  low = self & -self # only the lowest bit set
+  pos = -1
+  while low:
+    low >>= 1
+    pos += 1
+  return pos
+
+def bm_highest_bit(self) -> int:
+  pos = -1
+  while self:
+    self >>= 1
+    pos += 1
+  return pos
+
+def bm_items(self) -> Iterable[int]:
+  pos = 0
+  while self > 0:
+    if (self >> 1) << 1 != self:
+      yield pos
+    pos += 1
+    self >>= 1
+
+# Takes 2 iterations over our bitmap, one for highest_bit,
+# and then to find the rest of the bits.
+def bm_reverse_items(self) -> Iterable[int]:
+  pos = bm_highest_bit(self)
+  if pos >= 0:
+    yield pos
+  pos -= 1
+  while pos >= 0:
+    high = 1 << pos
+    if self & high:
+      yield pos
+    self -= high # unset high bit
+    pos -= 1