Skip to content

Commit 7b21f79

Browse files
committed
perf(runner): introduce RuntimeCpnet to replace linear scans
Add `ColouredFlow.Runner.RuntimeCpnet`, a runtime view that wraps the raw `ColouredPetriNet.t()` definition with precomputed indexes for the runner hot path. Build it once per request right after `Storage.get_flow_by_enactment/1` and thread it through enactment calibration, computation, occurrence, and workitem completion. Eliminates the prior `Enum.find/2` linear scans on every transition / arc / colour set / variable lookup, and removes the per-call `Utils.build_constants/1` and `Utils.build_of_type_context/1` rebuilds. The struct holds a reference to the original definition plus typed Maps keyed by node names, plus `arcs_by_transition_orientation` and `transitions_by_input_place`. `Utils.fetch_*` and `get_arcs_with_place/3` now consult the indexes; `list_transitions/2` reads from the inverted map. Storage interface, DB layout, and the GenServer state shape are all unchanged — this is a per-call view, not a cached cpnet (per the audit decision: "不把缓存塞进 GenServer state").
1 parent 4c66c1c commit 7b21f79

12 files changed

Lines changed: 556 additions & 195 deletions

File tree

lib/coloured_flow/enabled_binding_elements/computation.ex

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ defmodule ColouredFlow.EnabledBindingElements.Computation do
55

66
alias ColouredFlow.Definition.Arc
77
alias ColouredFlow.Definition.ColourSet
8-
alias ColouredFlow.Definition.ColouredPetriNet
98
alias ColouredFlow.Definition.Expression
109
alias ColouredFlow.Definition.Place
1110
alias ColouredFlow.Definition.Transition
1211
alias ColouredFlow.EnabledBindingElements.Binding
1312
alias ColouredFlow.Enactment.BindingElement
1413
alias ColouredFlow.Enactment.Marking
1514
alias ColouredFlow.MultiSet
15+
alias ColouredFlow.Runner.RuntimeCpnet
1616

1717
import ColouredFlow.EnabledBindingElements.Utils
1818

@@ -22,12 +22,12 @@ defmodule ColouredFlow.EnabledBindingElements.Computation do
2222
"""
2323
@spec list(
2424
transition :: Transition.t(),
25-
cpnet :: ColouredPetriNet.t(),
25+
runtime_cpnet :: RuntimeCpnet.t(),
2626
markings :: %{Place.name() => Marking.t()}
2727
) :: [BindingElement.t()]
28-
def list(transition, cpnet, markings) do
29-
inputs = get_arcs_with_place(transition, :p_to_t, cpnet)
30-
constants = build_constants(cpnet)
28+
def list(transition, %RuntimeCpnet{} = runtime_cpnet, markings) do
29+
inputs = get_arcs_with_place(transition, :p_to_t, runtime_cpnet)
30+
constants = runtime_cpnet.constants
3131

3232
arc_bindings =
3333
inputs
@@ -46,35 +46,35 @@ defmodule ColouredFlow.EnabledBindingElements.Computation do
4646
Binding.match_bag(marking.tokens, arc_bind_expr)
4747
end)
4848
end)
49-
|> reject_invalid_bandings(cpnet)
49+
|> reject_invalid_bandings(runtime_cpnet)
5050

5151
binding_combinations = Binding.combine(arc_bindings)
5252

5353
Enum.flat_map(binding_combinations, fn binding ->
54-
build_binding_element(inputs, binding, transition, constants, markings, cpnet)
54+
build_binding_element(inputs, binding, transition, constants, markings, runtime_cpnet)
5555
end)
5656
end
5757

58-
defp build_binding_element(inputs, binding, transition, constants, markings, cpnet) do
59-
case collect_consumption(inputs, binding, transition, constants, markings, cpnet) do
58+
defp build_binding_element(inputs, binding, transition, constants, markings, runtime_cpnet) do
59+
case collect_consumption(inputs, binding, transition, constants, markings, runtime_cpnet) do
6060
:error -> []
6161
to_consume -> [BindingElement.new(transition.name, binding, to_consume)]
6262
end
6363
end
6464

65-
defp collect_consumption(inputs, binding, transition, constants, markings, cpnet) do
65+
defp collect_consumption(inputs, binding, transition, constants, markings, runtime_cpnet) do
6666
Enum.reduce_while(inputs, [], fn {arc, place}, acc ->
67-
try_consume(arc, place, binding, transition, constants, markings, cpnet, acc)
67+
try_consume(arc, place, binding, transition, constants, markings, runtime_cpnet, acc)
6868
end)
6969
end
7070

71-
defp try_consume(arc, place, binding, transition, constants, markings, cpnet, acc) do
71+
defp try_consume(arc, place, binding, transition, constants, markings, runtime_cpnet, acc) do
7272
arc_binding = merge_constants(binding, arc, constants)
7373

7474
with(
7575
{:ok, {coefficient, value}} <- eval_arc(arc, arc_binding),
76-
colour_set = fetch_colour_set!(place.colour_set, cpnet),
77-
of_type_context = build_of_type_context(cpnet),
76+
colour_set = fetch_colour_set!(place.colour_set, runtime_cpnet),
77+
of_type_context = runtime_cpnet.of_type_context,
7878
{:ok, ^value} <- ColourSet.Of.of_type(value, colour_set.type, of_type_context),
7979
guard_binding = merge_constants(binding, transition, constants),
8080
{:ok, true} <- eval_transition_guard(transition, guard_binding),
@@ -88,14 +88,14 @@ defmodule ColouredFlow.EnabledBindingElements.Computation do
8888
end
8989
end
9090

91-
@spec reject_invalid_bandings([[[BindingElement.binding()]]], ColouredPetriNet.t()) ::
91+
@spec reject_invalid_bandings([[[BindingElement.binding()]]], RuntimeCpnet.t()) ::
9292
[[[BindingElement.binding()]]]
93-
defp reject_invalid_bandings(bindings_list, cpnet) do
94-
of_type_context = build_of_type_context(cpnet)
93+
defp reject_invalid_bandings(bindings_list, %RuntimeCpnet{} = runtime_cpnet) do
94+
of_type_context = runtime_cpnet.of_type_context
9595

9696
valid_binding? = fn binding ->
9797
Enum.all?(binding, fn {name, value} ->
98-
variable = fetch_variable!(name, cpnet)
98+
variable = fetch_variable!(name, runtime_cpnet)
9999

100100
match?(
101101
{:ok, _value},

lib/coloured_flow/enabled_binding_elements/occurrence.ex

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ defmodule ColouredFlow.EnabledBindingElements.Occurrence do
66

77
alias ColouredFlow.Definition.Arc
88
alias ColouredFlow.Definition.ColourSet
9-
alias ColouredFlow.Definition.ColouredPetriNet
109
alias ColouredFlow.Definition.Variable
1110
alias ColouredFlow.Enactment.BindingElement
1211
alias ColouredFlow.Enactment.Marking
1312
alias ColouredFlow.Enactment.Occurrence
1413
alias ColouredFlow.MultiSet
14+
alias ColouredFlow.Runner.RuntimeCpnet
1515

1616
import ColouredFlow.EnabledBindingElements.Utils
1717

@@ -22,23 +22,23 @@ defmodule ColouredFlow.EnabledBindingElements.Occurrence do
2222
@spec occur(
2323
binding_element :: BindingElement.t(),
2424
free_binding :: [{Variable.name(), ColourSet.value()}],
25-
cpnet :: ColouredPetriNet.t()
25+
runtime_cpnet :: RuntimeCpnet.t()
2626
) :: {:ok, Occurrence.t()} | {:error, [Exception.t()]}
27-
def occur(binding_element, free_binding, cpnet) do
28-
transition = fetch_transition!(binding_element.transition, cpnet)
27+
def occur(binding_element, free_binding, %RuntimeCpnet{} = runtime_cpnet) do
28+
transition = fetch_transition!(binding_element.transition, runtime_cpnet)
2929

30-
constants = build_constants(cpnet)
30+
constants = runtime_cpnet.constants
3131
binding = Enum.concat(binding_element.binding, free_binding)
3232

33-
outputs = get_arcs_with_place(transition, :t_to_p, cpnet)
33+
outputs = get_arcs_with_place(transition, :t_to_p, runtime_cpnet)
3434

3535
{to_produce, exceptions} =
3636
Enum.map_reduce(outputs, [], fn {arc, place}, acc ->
3737
# place constants into binding
3838
binding = merge_constants(binding, arc, constants)
3939

4040
with {:ok, result} <- ColouredFlow.Expression.eval(arc.expression.expr, binding),
41-
{:ok, tokens} <- build_tokens(result, place.colour_set, cpnet) do
41+
{:ok, tokens} <- build_tokens(result, place.colour_set, runtime_cpnet) do
4242
{%Marking{place: place.name, tokens: tokens}, acc}
4343
else
4444
{:error, exceptions} -> {:error, acc ++ exceptions}
@@ -70,15 +70,15 @@ defmodule ColouredFlow.EnabledBindingElements.Occurrence do
7070
# credo:disable-for-next-line JetCredo.Checks.ExplicitAnyType
7171
binding :: term(),
7272
colour_set :: ColourSet.name(),
73-
cpnet :: ColouredPetriNet.t()
73+
runtime_cpnet :: RuntimeCpnet.t()
7474
) :: {:ok, MultiSet.t()} | {:error, [Exception.t()]}
75-
defp build_tokens({coefficient, value}, colour_set, cpnet)
75+
defp build_tokens({coefficient, value}, colour_set, %RuntimeCpnet{} = runtime_cpnet)
7676
when is_integer(coefficient) and coefficient >= 0 do
7777
alias ColouredFlow.Definition.ColourSet.ColourSetMismatch
7878
alias ColouredFlow.Definition.ColourSet.Of
7979

80-
colour_set = fetch_colour_set!(colour_set, %ColouredPetriNet{} = cpnet)
81-
context = build_of_type_context(cpnet)
80+
colour_set = fetch_colour_set!(colour_set, runtime_cpnet)
81+
context = runtime_cpnet.of_type_context
8282

8383
case Of.of_type(value, colour_set.type, context) do
8484
{:ok, value} ->
Lines changed: 38 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,48 @@
11
defmodule ColouredFlow.EnabledBindingElements.Utils do
22
@moduledoc """
33
The utils for the enabled binding elements.
4+
5+
These helpers operate on a `ColouredFlow.Runner.RuntimeCpnet` — the indexed view
6+
built from a raw `ColouredFlow.Definition.ColouredPetriNet` at the runner-call
7+
boundary. Lookups are `O(1)` against the precomputed indexes.
48
"""
59

610
alias ColouredFlow.Definition.Arc
711
alias ColouredFlow.Definition.ColourSet
8-
alias ColouredFlow.Definition.ColouredPetriNet
912
alias ColouredFlow.Definition.Place
1013
alias ColouredFlow.Definition.Transition
1114
alias ColouredFlow.Definition.Variable
1215
alias ColouredFlow.Enactment.Marking
1316
alias ColouredFlow.MultiSet
17+
alias ColouredFlow.Runner.RuntimeCpnet
1418

15-
@spec fetch_colour_set!(colour_set :: ColourSet.name(), cpnet :: ColouredPetriNet.t()) ::
19+
@spec fetch_colour_set!(colour_set :: ColourSet.name(), runtime_cpnet :: RuntimeCpnet.t()) ::
1620
ColourSet.t()
17-
def fetch_colour_set!(colour_set, %ColouredPetriNet{} = cpnet) do
18-
Enum.find(
19-
cpnet.colour_sets,
20-
&match?(%ColourSet{name: ^colour_set}, &1)
21-
) || raise "Colour set with name #{inspect(colour_set)} not found in the petri net."
22-
end
23-
24-
@spec build_of_type_context(cpnet :: ColouredPetriNet.t()) ::
25-
ColouredFlow.Definition.ColourSet.Of.context()
26-
def build_of_type_context(%ColouredPetriNet{} = cpnet) do
27-
types =
28-
Map.new(cpnet.colour_sets, fn %ColourSet{name: name, type: type} ->
29-
{name, type}
30-
end)
31-
32-
%{
33-
fetch_type: fn name ->
34-
case Map.fetch(types, name) do
35-
:error -> raise "Colour set with name #{inspect(name)} not found in the petri net."
36-
{:ok, type} -> {:ok, type}
37-
end
38-
end
39-
}
21+
def fetch_colour_set!(colour_set, %RuntimeCpnet{colour_sets: colour_sets}) do
22+
case Map.fetch(colour_sets, colour_set) do
23+
{:ok, cs} -> cs
24+
:error -> raise "Colour set with name #{inspect(colour_set)} not found in the petri net."
25+
end
4026
end
4127

42-
@spec fetch_variable!(variable :: Variable.name(), cpnet :: ColouredPetriNet.t()) ::
28+
@spec fetch_variable!(variable :: Variable.name(), runtime_cpnet :: RuntimeCpnet.t()) ::
4329
Variable.t()
44-
def fetch_variable!(variable, %ColouredPetriNet{} = cpnet) do
45-
Enum.find(
46-
cpnet.variables,
47-
&match?(%Variable{name: ^variable}, &1)
48-
) || raise "Variable with name #{inspect(variable)} not found in the petri net."
30+
def fetch_variable!(variable, %RuntimeCpnet{variables: variables}) do
31+
case Map.fetch(variables, variable) do
32+
{:ok, var} -> var
33+
:error -> raise "Variable with name #{inspect(variable)} not found in the petri net."
34+
end
4935
end
5036

51-
@spec get_arcs_with_place(Transition.t(), Arc.orientation(), ColouredPetriNet.t()) ::
52-
Enumerable.t({Arc.t(), Place.t()})
53-
def get_arcs_with_place(%Transition{} = transition, orientation, %ColouredPetriNet{} = cpnet)
37+
@spec get_arcs_with_place(Transition.t(), Arc.orientation(), RuntimeCpnet.t()) ::
38+
[{Arc.t(), Place.t()}]
39+
def get_arcs_with_place(
40+
%Transition{name: transition_name},
41+
orientation,
42+
%RuntimeCpnet{arcs_by_transition_orientation: index}
43+
)
5444
when orientation in [:p_to_t, :t_to_p] do
55-
%{name: transition_name} = transition
56-
57-
cpnet.arcs
58-
|> Stream.flat_map(fn
59-
%Arc{orientation: ^orientation, transition: ^transition_name} = arc -> [arc]
60-
%Arc{} -> []
61-
end)
62-
|> Stream.map(fn %Arc{place: place_name} = arc ->
63-
place =
64-
Enum.find(cpnet.places, &match?(%Place{name: ^place_name}, &1)) ||
65-
raise "Place with name #{inspect(place_name)} not found in the petri net."
66-
67-
{arc, place}
68-
end)
45+
Map.get(index, {transition_name, orientation}, [])
6946
end
7047

7148
@spec get_marking(Place.t(), %{Place.name() => Marking.t()}) :: Marking.t()
@@ -77,40 +54,22 @@ defmodule ColouredFlow.EnabledBindingElements.Utils do
7754
)
7855
end
7956

80-
@spec fetch_transition!(name :: Transition.name(), cpnet :: ColouredPetriNet.t()) ::
57+
@spec fetch_transition!(name :: Transition.name(), runtime_cpnet :: RuntimeCpnet.t()) ::
8158
Transition.t()
82-
def fetch_transition!(name, %ColouredPetriNet{} = cpnet) do
83-
case Enum.find(cpnet.transitions, &(&1.name == name)) do
84-
nil -> raise "Transition not found: #{name}"
85-
transition -> transition
59+
def fetch_transition!(name, %RuntimeCpnet{transitions: transitions}) do
60+
case Map.fetch(transitions, name) do
61+
{:ok, transition} -> transition
62+
:error -> raise "Transition not found: #{name}"
8663
end
8764
end
8865

89-
@spec list_transitions(in_places :: Enumerable.t(Place.name()), cpnet :: ColouredPetriNet.t()) ::
90-
Enumerable.t(Transition.t())
91-
def list_transitions(in_places, %ColouredPetriNet{} = cpnet) when is_list(in_places) do
92-
in_places = MapSet.new(in_places)
93-
94-
cpnet.arcs
95-
|> Enum.flat_map(fn
96-
%Arc{orientation: :p_to_t} = arc ->
97-
if arc.place in in_places do
98-
[arc.transition]
99-
else
100-
[]
101-
end
102-
103-
%Arc{} ->
104-
[]
105-
end)
106-
|> MapSet.new()
107-
|> then(fn transition_names ->
108-
Enum.filter(cpnet.transitions, &MapSet.member?(transition_names, &1.name))
109-
end)
110-
end
111-
112-
@spec build_constants(ColouredPetriNet.t()) :: %{ColourSet.name() => ColourSet.value()}
113-
def build_constants(%ColouredPetriNet{} = cpnet) do
114-
Map.new(cpnet.constants, &{&1.name, &1.value})
66+
@spec list_transitions(
67+
in_places :: Enumerable.t(Place.name()),
68+
runtime_cpnet :: RuntimeCpnet.t()
69+
) :: [Transition.t()]
70+
def list_transitions(in_places, %RuntimeCpnet{transitions_by_input_place: index}) do
71+
in_places
72+
|> Enum.flat_map(fn place_name -> Map.get(index, place_name, []) end)
73+
|> Enum.uniq_by(& &1.name)
11574
end
11675
end

lib/coloured_flow/runner/enactment/enactment.ex

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ defmodule ColouredFlow.Runner.Enactment do
5050
alias ColouredFlow.Runner.Enactment.WorkitemCompletion
5151
alias ColouredFlow.Runner.Enactment.WorkitemConsumption
5252
alias ColouredFlow.Runner.Exceptions
53+
alias ColouredFlow.Runner.RuntimeCpnet
5354
alias ColouredFlow.Runner.Storage
5455
alias ColouredFlow.Runner.Telemetry
5556

@@ -139,12 +140,12 @@ defmodule ColouredFlow.Runner.Enactment do
139140
end
140141

141142
def handle_continue(:calibrate_workitems, %__MODULE__{} = state) do
142-
cpnet = Storage.get_flow_by_enactment(state.enactment_id)
143-
calibration = WorkitemCalibration.initial_calibrate(state, cpnet)
143+
runtime_cpnet = build_runtime_cpnet(state.enactment_id)
144+
calibration = WorkitemCalibration.initial_calibrate(state, runtime_cpnet)
144145
state = apply_calibration(calibration)
145146

146147
# try to terminate at the start
147-
case check_termination(state, cpnet) do
148+
case check_termination(state, runtime_cpnet.definition) do
148149
{:stop, reason} -> {:stop, {:shutdown, reason}, state}
149150
:cont -> {:noreply, state, Lifespan.timeout(state)}
150151
end
@@ -159,8 +160,11 @@ defmodule ColouredFlow.Runner.Enactment do
159160
state = apply_calibration(calibration)
160161

161162
if transition in [:complete, :complete_e] do
162-
cpnet = Storage.get_flow_by_enactment(state.enactment_id)
163-
# try to terminate when the transition is `:complete` or `:complete_e`
163+
# try to terminate when the transition is `:complete` or `:complete_e`.
164+
# `complete_workitems/3` always threads the runtime cpnet through the
165+
# calibration options, so we reuse it here instead of re-fetching.
166+
%RuntimeCpnet{definition: cpnet} = Keyword.fetch!(options, :runtime_cpnet)
167+
164168
case check_termination(state, cpnet) do
165169
{:stop, reason} -> {:stop, {:shutdown, reason}, state}
166170
:cont -> {:noreply, state, Lifespan.timeout(state)}
@@ -496,10 +500,14 @@ defmodule ColouredFlow.Runner.Enactment do
496500
Enum.map(workitem_id_and_outputs, fn {workitem_id, free_binding} ->
497501
{Map.fetch!(started_workitems, workitem_id), free_binding}
498502
end),
499-
cpnet = Storage.get_flow_by_enactment(state.enactment_id),
500-
{:ok, workitem_occurrences} <- WorkitemCompletion.complete(workitem_and_outputs, cpnet)
503+
runtime_cpnet = build_runtime_cpnet(state.enactment_id),
504+
{:ok, workitem_occurrences} <-
505+
WorkitemCompletion.complete(workitem_and_outputs, runtime_cpnet)
501506
) do
502-
calibration_options = [cpnet: cpnet, workitem_occurrences: workitem_occurrences]
507+
calibration_options = [
508+
runtime_cpnet: runtime_cpnet,
509+
workitem_occurrences: workitem_occurrences
510+
]
503511

504512
{
505513
:ok,
@@ -515,6 +523,13 @@ defmodule ColouredFlow.Runner.Enactment do
515523
end
516524
end
517525

526+
@spec build_runtime_cpnet(enactment_id()) :: RuntimeCpnet.t()
527+
defp build_runtime_cpnet(enactment_id) do
528+
enactment_id
529+
|> Storage.get_flow_by_enactment()
530+
|> RuntimeCpnet.from_definition()
531+
end
532+
518533
@impl GenServer
519534
def handle_info(:timeout, state) do
520535
{:stop, {:shutdown, "Terminated due to inactivity"}, state}

0 commit comments

Comments
 (0)