瀏覽代碼

Turn State and Transition classes (many attribute accesses at runtime) and Event class (frequently created at runtime) into slotted classes for performance. State and Transition are 'frozen' after tree optimization (writing an attribute would raise an error)

Joeri Exelmans 5 年之前
父節點
當前提交
638185fbe3
共有 3 個文件被更改,包括 127 次插入63 次删除
  1. 18 5
      src/sccd/statechart/dynamic/event.py
  2. 94 58
      src/sccd/statechart/static/tree.py
  3. 15 0
      src/sccd/util/freezable.py

+ 18 - 5
src/sccd/statechart/dynamic/event.py

@@ -5,12 +5,17 @@ from typing import *
 from sccd.util.duration import *
 
 # A raised event.
-@dataclass(eq=True)
 class Event:
-    id: int
-    name: str
-    port: str = ""
-    params: List[Any] = field(default_factory=list)
+    __slots__ = ["id", "name", "port", "params"]
+
+    def __init__(self, id, name, port = "", params = []):
+        self.id: int = id
+        self.name: str = name
+        self.port: str = port
+        self.params: List[Any] = params
+
+    def __eq__(self, other):
+        return self.id == other.id and self.port == other.port and self.params == other.params
 
     def __str__(self):
         if self.port:
@@ -27,21 +32,29 @@ class Event:
 
 # Abstract class.
 class EventTarget(ABC):
+    __slots__ = []
+
     @abstractmethod
     def __init__(self):
         pass
 
 # A raised output event with a target and a time offset.
 class OutputEvent:
+    __slots__ = ["event", "target", "time_offset"]
+
     def __init__(self, event: Event, target: EventTarget, time_offset: int = (0)):
         self.event = event
         self.target = target
         self.time_offset = time_offset
 
 class OutputPortTarget(EventTarget):
+    __slots__ = ["outport"]
+
     def __init__(self, outport: str):
         self.outport = outport
 
 class InstancesTarget(EventTarget):
+    __slots__ = ["instances"]
+
     def __init__(self, instances):
         self.instances = instances

+ 94 - 58
src/sccd/statechart/static/tree.py

@@ -4,26 +4,30 @@ from sccd.statechart.static.action import *
 from sccd.util.bitmap import *
 from sccd.util import timer
 from sccd.util.visit_tree import *
+from sccd.util.freezable import *
 
-@dataclass
-class State:
-    short_name: str # value of 'id' attribute in XML
-    parent: Optional['State'] # only None if root state
-    # scope: Scope # states have their own scope: variables can be declared in <onentry>, subsequently read in guard conditions and actions.
+class State(Freezable):
+    __slots__ = ["short_name", "parent", "stable", "children", "default_state", "transitions", "enter", "exit", "opt"]
 
-    stable: bool = False # whether this is a stable stabe. this field is ignored if maximality semantics is not set to SYNTACTIC
+    def __init__(self, short_name: str, parent: Optional['State']):
+        super().__init__()
 
-    children: List['State'] = field(default_factory=list)
-    default_state: 'State' = None # child state pointed to by 'initial' attribute
+        self.short_name: str = short_name # value of 'id' attribute in XML
+        self.parent: Optional['State'] = parent # only None if root state
 
-    transitions: List['Transition'] = field(default_factory=list)
+        self.stable: bool = False # whether this is a stable stabe. this field is ignored if maximality semantics is not set to SYNTACTIC
 
-    enter: List[Action] = field(default_factory=list)
-    exit: List[Action] = field(default_factory=list)
+        self.children: List['State'] = []
+        self.default_state: 'State' = None # child state pointed to by 'initial' attribute
 
-    opt: Optional['StateOptimization'] = None
+        self.transitions: List['Transition'] = []
 
-    def __post_init__(self):
+        self.enter: List[Action] = []
+        self.exit: List[Action] = []
+
+        self.opt: Optional['StateOptimization'] = None
+
+    # def __post_init__(self):
         if self.parent is not None:
             self.parent.children.append(self)
 
@@ -33,9 +37,13 @@ class State:
     def __repr__(self):
         return "State(\"%s\")" % (self.short_name)
 
-@dataclass
 class HistoryState(State):
-    history_id: Optional[int] = None
+    __slots__ = ["history_id"]
+
+    def __init__(self, short_name: str, parent: Optional['State']):
+        super().__init__(short_name, parent)
+
+        self.history_id: Optional[int] = None
 
     # Set of states that may be history values.
     @abstractmethod
@@ -73,6 +81,8 @@ class ParallelState(State):
 
 @dataclass
 class EventDecl:
+    __slots__ = ["id", "name", "params_decl"]
+
     id: int
     name: str
     params_decl: List[ParamDecl]
@@ -85,6 +95,8 @@ class EventDecl:
 
 @dataclass
 class Trigger:
+    __slots__ = ["enabling", "enabling_bitmap"]
+
     enabling: List[EventDecl]
 
     def __post_init__(self):
@@ -120,6 +132,8 @@ class Trigger:
 
 @dataclass
 class NegatedTrigger(Trigger):
+    __slots__ = ["disabling", "disabling_bitmap"]
+
     disabling: List[EventDecl]
 
     def __post_init__(self):
@@ -151,66 +165,81 @@ class AfterTrigger(Trigger):
     def copy_params_to_stack(self, ctx: EvalContext):
         pass
 
-@dataclass
-class Transition:
-    source: State
-    targets: List[State]
-    scope: Scope
+class Transition(Freezable):
+    __slots__ = ["source", "targets", "scope", "target_string", "guard", "actions", "trigger", "opt"]
+
+    def __init__(self, source: State, targets: List[State], scope: Scope, target_string: Optional[str] = None):
+        super().__init__()
+
+        self.source: State = source
+        self.targets: List[State] = targets
+        self.scope: Scope = scope
 
-    target_string: Optional[str] = None
+        self.target_string: Optional[str] = target_string
 
-    guard: Optional[Expression] = None
-    actions: List[Action] = field(default_factory=list)
-    trigger: Optional[Trigger] = None
+        self.guard: Optional[Expression] = None
+        self.actions: List[Action] = []
+        self.trigger: Optional[Trigger] = None
 
-    opt: Optional['TransitionOptimization'] = None        
+        self.opt: Optional['TransitionOptimization'] = None        
                     
     def __str__(self):
         return termcolor.colored("%s 🡪 %s" % (self.source.opt.full_name, self.targets[0].opt.full_name), 'green')
 
 
 # Data that is generated for each state.
-@dataclass
-class StateOptimization:
-    full_name: str = ""
-
-    state_id: int = -1
-    state_id_bitmap: Bitmap = Bitmap() # bitmap with only state_id-bit set
+class StateOptimization(Freezable):
+    __slots__ = ["full_name", "state_id", "state_id_bitmap", "ancestors", "descendants", "history", "ts_static", "ts_dynamic", "after_triggers"]
+    def __init__(self):
+        super().__init__()
 
-    ancestors: Bitmap = Bitmap()
-    descendants: Bitmap = Bitmap()
+        self.full_name: str = ""
 
-    # Subset of children that are HistoryState.
-    # For each item, the second element of the tuple is the "history mask".
-    history: List[Tuple[HistoryState, Bitmap]] = field(default_factory=list)
+        self.state_id: int = -1
+        self.state_id_bitmap: Bitmap = Bitmap() # bitmap with only state_id-bit set
 
-    # Subset of descendants that are always entered when this state is the target of a transition
-    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)
+        self.ancestors: Bitmap = Bitmap()
+        self.descendants: Bitmap = Bitmap()
 
-    # Triggers of outgoing transitions that are AfterTrigger.
-    after_triggers: List[AfterTrigger] = field(default_factory=list)
+        # Subset of children that are HistoryState.
+        # For each item, the second element of the tuple is the "history mask".
+        self.history: List[Tuple[HistoryState, Bitmap]] = []
 
+        # Subset of descendants that are always entered when this state is the target of a transition
+        self.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.
+        self.ts_dynamic: List[HistoryState] = []
 
-# Data that is generated for each transition.
-@dataclass(frozen=True)
-class TransitionOptimization:
-    arena: State
-    arena_bitmap: Bitmap
-    enter_states_static: Bitmap # The "enter set" can be computed partially statically, and if there are no history states in it, entirely statically
-    enter_states_dynamic: List[HistoryState] # The part of the "enter set" that cannot be computed statically.
+        # Triggers of outgoing transitions that are AfterTrigger.
+        self.after_triggers: List[AfterTrigger] = []
 
 
-@dataclass
-class StateTree:
-    root: State
-    transition_list: List[Transition] # depth-first document order
-    state_list: List[State] # depth-first document order
-    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
+# Data that is generated for each transition.
+class TransitionOptimization(Freezable):
+    __slots__ = ["arena", "arena_bitmap", "enter_states_static", "enter_states_dynamic"]
+
+    def __init__(self, arena: State, arena_bitmap: Bitmap, enter_states_static: Bitmap, enter_states_dynamic: List[HistoryState]):
+        super().__init__()
+        self.arena: State = arena
+        self.arena_bitmap: Bitmap = arena_bitmap
+        self.enter_states_static: Bitmap = enter_states_static # The "enter set" can be computed partially statically, and if there are no history states in it, entirely statically
+        self.enter_states_dynamic: List[HistoryState] = enter_states_dynamic # The part of the "enter set" that cannot be computed statically.
+        self.freeze()
+
+
+class StateTree(Freezable):
+    __slots__ = ["root", "transition_list", "state_list", "state_dict", "after_triggers", "stable_bitmap", "history_states"]
+
+    def __init__(self, root: State, transition_list: List[Transition], state_list: List[State], state_dict: Dict[str, State], after_triggers: List[AfterTrigger], stable_bitmap: Bitmap, history_states: List[HistoryState]):
+        super().__init__()
+        self.root: State = root
+        self.transition_list: List[Transition] = transition_list # depth-first document order
+        self.state_list: List[State] = state_list # depth-first document order
+        self.state_dict: Dict[str, State] = state_dict # mapping from 'full name' to State
+        self.after_triggers: List[AfterTrigger] = after_triggers # all after-triggers in the statechart
+        self.stable_bitmap: Bitmap = stable_bitmap # set of states that are syntactically marked 'stable'
+        self.history_states: List[HistoryState] = history_states # all the history states in the statechart
+        self.freeze()
 
 # Reduce a list of states to a set of states, as a bitmap
 def states_to_bitmap(state_list: List[State]) -> Bitmap:
@@ -293,6 +322,10 @@ def optimize_tree(root: State) -> StateTree:
             if isinstance(c, HistoryState):
                 state.opt.history.append((c, c.history_mask()))
 
+    def freeze(state: State, _=None):
+        state.freeze()
+        state.opt.freeze()
+
     visit_tree(root, lambda s: s.children,
         before_children=[
             init_opt(),
@@ -304,6 +337,7 @@ def optimize_tree(root: State) -> StateTree:
             set_descendants,
             add_history,
             set_static_target_states,
+            freeze,
         ])
 
 
@@ -352,6 +386,8 @@ def optimize_tree(root: State) -> StateTree:
             enter_states_static=enter_states_static,
             enter_states_dynamic=enter_states_dynamic)
 
+        t.freeze()
+
 
     timer.stop("optimize tree")
 

+ 15 - 0
src/sccd/util/freezable.py

@@ -0,0 +1,15 @@
+
+# Slight performance hit when setting an attribute.
+# Read operations unchanged.
+class Freezable:
+    __slots__ = ["_Freezable__frozen"]
+
+    def __init__(self):
+        super().__setattr__("_Freezable__frozen", False)
+
+    def freeze(self):
+        super().__setattr__("_Freezable__frozen", True)
+
+    def __setattr__(self, key, value):
+        assert not self._Freezable__frozen
+        super().__setattr__(key, value)