瀏覽代碼

Event-based transition candidate generation algorithm prepares its cache for all single-item sets of events.

Joeri Exelmans 5 年之前
父節點
當前提交
5468ca694c

+ 60 - 24
src/sccd/statechart/dynamic/round.py

@@ -1,4 +1,5 @@
 from typing import *
+from sccd.statechart.static.statechart import Statechart
 from sccd.statechart.dynamic.event import *
 from sccd.util.bitmap import *
 from sccd.statechart.static.tree import *
@@ -6,46 +7,81 @@ from sccd.util.debug import *
 from sccd.action_lang.dynamic.exceptions import *
 from sccd.util import timer
 
-class CandidatesGenerator:
-    def __init__(self, reverse: bool):
-        self.reverse = reverse
-        self.cache = {}
+@dataclass
+class CacheCounter:
+    cache_hits = 0
+    cache_misses = 0
+
+ctr = CacheCounter()
+
+if timer.TIMINGS:
+    import atexit
+    def print_stats():
+        print("\ncache hits: %s, cache misses: %s" %(ctr.cache_hits, ctr.cache_misses))
+    atexit.register(print_stats)
+
+@dataclass
+class CandidatesGenerator(ABC):
+    statechart: Statechart
+    reverse: bool
+    cache: Dict[Tuple[Bitmap,Bitmap], List[Transition]] = field(default_factory=dict)
+
+    @abstractmethod
+    def generate(self, state, enabled_events: List[Event], forbidden_arenas: Bitmap) -> Iterable[Transition]:
+        pass
 
 class CandidatesGeneratorCurrentConfigBased(CandidatesGenerator):
+
+    def _candidates(self, config_bitmap, forbidden_arenas):
+        candidates = [ t for state_id in config_bitmap.items()
+                          for t in self.statechart.tree.state_list[state_id].transitions
+                           if (not forbidden_arenas & t.opt.arena_bitmap) ]
+        if self.reverse:
+            candidates.reverse()
+        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)
-        key = (state.configuration_bitmap, forbidden_arenas)
+        key = (state.configuration, forbidden_arenas)
 
         try:
             candidates = self.cache[key]
+            ctr.cache_hits += 1
         except KeyError:
-            candidates = self.cache[key] = [
-                t for s in state.configuration
-                    if (not forbidden_arenas & s.opt.state_id_bitmap)
-                    for t in s.transitions
-                ]
-            if self.reverse:
-                candidates.reverse()
+            candidates = self.cache[key] = self._candidates(state.configuration, forbidden_arenas)
+            ctr.cache_misses += 1
 
         def filter_f(t):
             return (not t.trigger or t.trigger.check(events_bitmap)) and state.check_guard(t, enabled_events)
         return filter(filter_f, candidates)
 
+@dataclass
 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.internal_events.items():
+            events_bitmap = bit(event_id)
+            self.cache[(events_bitmap, 0)] = self._candidates(events_bitmap, 0)
+
+    def _candidates(self, events_bitmap, forbidden_arenas):
+        candidates = [ t for t in self.statechart.tree.transition_list
+                          if (not t.trigger or t.trigger.check(events_bitmap)) # todo: check port?
+                          and (not forbidden_arenas & t.opt.arena_bitmap) ]
+        if self.reverse:
+            candidates.reverse()
+        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)
         key = (events_bitmap, forbidden_arenas)
 
         try:
             candidates = self.cache[key]
+            ctr.cache_hits += 1
         except KeyError:
-            candidates = self.cache[key] = [
-                t for t in state.statechart.tree.transition_list
-                    if (not t.trigger or t.trigger.check(events_bitmap)) # todo: check port?
-                    and (not forbidden_arenas & t.source.opt.state_id_bitmap)
-                ]
-            if self.reverse:
-                candidates.reverse()
+            candidates = self.cache[key] = self._candidates(events_bitmap, forbidden_arenas)
+            ctr.cache_misses += 1
 
         def filter_f(t):
             return state.check_source(t) and state.check_guard(t, enabled_events)
@@ -209,10 +245,10 @@ class SmallStep(Round):
         arenas = Bitmap()
         stable_arenas = Bitmap()
 
-        timer.start("get candidate")
+        timer.start("candidate generation")
         candidates = get_candidates(0)
         t = next(candidates, None)
-        timer.stop("get candidate")
+        timer.stop("candidate generation")
         while t:
             arena = t.opt.arena_bitmap
             if not (arenas & arena):
@@ -227,13 +263,13 @@ class SmallStep(Round):
 
                 # need to re-generate candidates after firing transition
                 # because possibly the set of current events has changed
-                timer.start("get candidate")
+                timer.start("candidate generation")
                 candidates = get_candidates(extra_forbidden=arenas)
             else:
-                timer.start("get candidate")
+                timer.start("candidate generation")
 
             t = next(candidates, None)
-            timer.stop("get candidate")
+            timer.stop("candidate generation")
 
         return (arenas, stable_arenas)
 

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

@@ -37,8 +37,8 @@ class StatechartInstance(Instance):
         reverse = semantics.priority == Priority.SOURCE_CHILD
 
         # 2 transition candidate generation algorithms to choose from!
-        # generator = CandidatesGeneratorCurrentConfigBased(reverse)
-        generator = CandidatesGeneratorEventBased(reverse)
+        # generator = CandidatesGeneratorCurrentConfigBased(statechart, reverse)
+        generator = CandidatesGeneratorEventBased(statechart, reverse)
 
         # Big step + combo step maximality semantics
 

+ 2 - 0
src/sccd/statechart/parser/xml.py

@@ -27,6 +27,7 @@ def create_statechart_parser(globals, src_file, load_external = True, parse = pa
         semantics=SemanticConfiguration(),
         scope=Scope("instance", parent=BuiltIn),
         datamodel=None,
+        internal_events=Bitmap(),
         inport_events={},
         event_outport={},
         tree=None,
@@ -99,6 +100,7 @@ def create_statechart_parser(globals, src_file, load_external = True, parse = pa
             if port is None:
               # internal event
               event_id = globals.events.assign_id(event_name)
+              statechart.internal_events |= event_id
               return RaiseInternalEvent(name=event_name, params=params, event_id=event_id)
             else:
               # output event - no ID in global namespace

+ 1 - 0
src/sccd/statechart/static/statechart.py

@@ -89,6 +89,7 @@ class Statechart:
   scope: Scope
   datamodel: Optional[Block] # block of statements setting up the datamodel (variables in instance scope)
 
+  internal_events: Bitmap
   inport_events: Dict[str, Set[int]] # mapping from inport to set of event IDs
   event_outport: Dict[str, str] # mapping from event name to outport
 

+ 17 - 12
src/sccd/statechart/static/tree.py

@@ -28,7 +28,7 @@ class State:
             self.parent.children.append(self)
 
     def target_states(self, instance) -> Bitmap:
-        return self.opt.static_ts_bitmap | functools.reduce(lambda x,y: x|y, (s.target_states(instance) for s in self.opt.dynamic_ts), Bitmap())
+        return self.opt.ts_static | functools.reduce(lambda x,y: x|y, (s.target_states(instance) for s in self.opt.ts_dynamic), Bitmap())
 
     def additional_target_states(self, instance) -> Bitmap:
         return self.opt.state_id_bitmap
@@ -47,10 +47,15 @@ class StateOptimization:
     ancestors: Bitmap = Bitmap()
     descendants: Bitmap = Bitmap()
 
-    history: List[Tuple[State, Bitmap]] = field(default_factory=list) # subset of children
-    static_ts_bitmap: Bitmap = Bitmap() # Subset of descendants that are always entered when this state is the target of a transition
-    dynamic_ts: List[State] = field(default_factory=list) # Subset of descendants that MAY be entered when this state is the target of a transition, depending on the history values.
+    # subset of children that are HistoryState
+    history: List[Tuple[State, Bitmap]] = field(default_factory=list)
 
+    # Subset of descendants that are always entered when this state is the target of a transition
+    ts_static: Bitmap = Bitmap() 
+    # Subset of descendants that MAY be entered when this state is the target of a transition, depending on the history values.
+    ts_dynamic: List[State] = field(default_factory=list) 
+
+    # Triggers of outgoing transitions that are AfterTrigger.
     after_triggers: List['AfterTrigger'] = field(default_factory=list)
 
 class HistoryState(State):
@@ -262,18 +267,18 @@ class StateTree:
 
         def set_static_target_states(state: State, _):
             if isinstance(state, ParallelState):
-                state.opt.static_ts_bitmap = reduce(lambda x,y: x|y, (s.opt.static_ts_bitmap for s in state.children), state.opt.state_id_bitmap)
-                state.opt.dynamic_ts = list(itertools.chain.from_iterable(c.opt.dynamic_ts for c in state.children if not isinstance(c, HistoryState)))
+                state.opt.ts_static = reduce(lambda x,y: x|y, (s.opt.ts_static for s in state.children), state.opt.state_id_bitmap)
+                state.opt.ts_dynamic = list(itertools.chain.from_iterable(c.opt.ts_dynamic for c in state.children if not isinstance(c, HistoryState)))
             elif isinstance(state, HistoryState):
-                state.opt.static_ts_bitmap = Bitmap()
-                state.opt.dynamic_ts = state.children
+                state.opt.ts_static = Bitmap()
+                state.opt.ts_dynamic = state.children
             else: # "regular" state:
                 if state.default_state:
-                    state.opt.static_ts_bitmap = state.opt.state_id_bitmap | state.default_state.opt.static_ts_bitmap
-                    state.opt.dynamic_ts = state.default_state.opt.dynamic_ts
+                    state.opt.ts_static = state.opt.state_id_bitmap | state.default_state.opt.ts_static
+                    state.opt.ts_dynamic = state.default_state.opt.ts_dynamic
                 else:
-                    state.opt.static_ts_bitmap = state.opt.state_id_bitmap
-                    state.opt.dynamic_ts = []
+                    state.opt.ts_static = state.opt.state_id_bitmap
+                    state.opt.ts_dynamic = []
 
         def add_history(state: State, _= None):
             for c in state.children:

+ 2 - 0
test/lib/test_parser.py

@@ -72,8 +72,10 @@ def create_test_parser(create_statechart_parser):
           globals,
           Statechart(
             semantics=variant,
+            #  All other fields remain the same
             scope=statechart.scope,
             datamodel=statechart.datamodel,
+            internal_events=statechart.internal_events,
             inport_events=statechart.inport_events,
             event_outport=statechart.event_outport,
             tree=statechart.tree)),