-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Discrete Control performance improvements based on AGV model #1529
Conversation
Could you provide some timings/graphs on the performance of the model, how you found these bottlenecks and how this addresses them? |
@evetion this could be the topic of the next knowledge sharing, but here's a quick view of the profiles of the AGV model (4 simulated days). |
Here's a more elaborate breakdown of the changes to discrete control in this PR. Avoiding allocationThis is a straightforward one, there was memory allocation each time a truth state (a string of 'T' and 'F' characters) was determined: Lines 295 to 303 in 89b8574
To get around this, I pre-allocate memory for the longest possible truth state and use that every time a truth state is determined: Lines 757 to 763 in 77a2f0d
I also changed the truth states from strings to Boolean vectors, which is possible now that we don't have the 'U' and 'D' truth values anymore. The input and output are still written as strings of 'T' and 'F'. Type stabilityThis is a more interesting one. This part of the code was very detrimental to performance: Lines 396 to 412 in 89b8574
That is because this code is type unstable; a priori the type of To get around this, I:
Lines 193 to 214 in 77a2f0d
Lines 1210 to 1215 in 77a2f0d
Lines 401 to 526 in 77a2f0d
Notes on complexity and maintainabilityThe code above makes the discrete control code more explicit, which I think makes it easier to understand but also increases maintenance load. For instance, if we want to support discrete control for a new variable or node type, this needs to be added to Maybe the discrete control code should also get its own script (again). |
nice explanation. thanks! |
Funny thing to play with, slightly relevant: julia> macro b_str(v); collect(v).=='T'; end
@b_str (macro with 1 method)
julia> b"TTF"
3-element BitVector:
1
1
0 edit: Note that b_str already exists in Base, so would be good to choose another prefix: help?> @b_str
@b_str
Create an immutable byte (UInt8) vector using string syntax. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice to see another performance increase! What are the absolute runtimes/relative part of control of/in the overall runtime?
Seems like some good changes, but I wonder what you think of your solution using the ParameterUpdate
struct. I see two red flags in its design, that you have to check for nan, and the check for the correct type. Let me know if you want help on refactoring it further.
As an experiment, seeing that this is a documentation sprint as well, might I suggest to try to go overboard in adding inline comments/docstrings literally everywhere? Just to see whether that results in something we're happy with. I will take it on me to review and edit those comments myself later on.
core/src/callback.jl
Outdated
n_greater_than = length(truth_values_variable) | ||
discrete_control.truth_state[truth_value_idx:(truth_value_idx + n_greater_than - 1)] .= | ||
truth_values_variable | ||
variable_idx += 1 | ||
truth_value_idx += n_greater_than | ||
end | ||
|
||
# The maximum truth state length is possibly shorter than this truth state | ||
truth_state = view(discrete_control.truth_state, 1:(truth_value_idx - 1)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are certain assumptions here on the possible interplays between the different input tables, but I would be really helped to by some inline documentation of such tables.
Both the comment # Build up the truth state from the truth values per (compound) variable per greater than
and # The maximum truth state length is possibly shorter than this truth state
don't help me much here.
Random thought; we could split such functionality into a function and have a docstring including a doctest?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doctest?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, providing an example (and testing it) as part of the docstring https://documenter.juliadocs.org/stable/man/doctests/.
I have refactored several smaller things several times, but have not found significant improvements, apart from the commit I did. Things that do not pan out:
This PR is 2-3x faster than the current main, mostly because we went from 35M to 2M allocations. Latest flamegraph with two observations: Control takes roughly a third of the runtime (originally 70%). Of that time, a third is spent in purely looping over all control conditions ( Wrapping up, getting this faster requires a more significant refactor. Besides, I believe the current code needs a refactor, as it introduced a lot of boilerplate, with repetition and fields also existing elsewhere. This is discussed partially in #1539. Depending on the priority of getting the AGV model faster, we can either merge this, and promise to follow up soon, or keep this open until we refactor. |
Based on the discussion of today, let's refactor while we can! We see several steps, each one slightly more complex than the last, would be good to implement them one by one, and see how performance/complexity behaves. It's fine to stop after step 1 or 2 if this takes too long for too little benefit.
|
Status updateCurrent profile of running the AGV model: Zoomed in on the discrete control section: So there is some runtime dispatch at where now the generic parameter update loop is: Lines 442 to 456 in b9f3ee0
but it is not significant (note that we do not know how this behaves in general, the AGV only/mainly has control of pumps). Running the 4 day simulation now takes ~3.3 seconds. The latest commits got rid of quite some complexity/nastiness:
There are still some lookups/loops that can probably be sped up. Also it would be nice if this can be implemented in a more generic way without significant loss of performance: Lines 458 to 477 in b9f3ee0
To me this looks like an interesting use case for a custom macro, but I haven't been able to figure that out. |
@evetion I also now implemented your point 2, now I am down to a runtime of ~2.7 s. I'm now also wondering whether I need this whole part at all: Lines 304 to 341 in ace0b10
That is, why have separate truth values per variable and then copy them to the full truth state. I think this was relevant when we considered that variables could be listened to by multiple discrete control nodes, but now (compound) variables are discrete control node specific so we don't need that anymore. Probably the change in truth state can already be detected in |
Tests now probably fail because a control state change check should be done at the beginning of the simulation regardless of truth state change. Some thoughts on refactoring the |
…ition!`, introduce separate struct for compound variables
Another big step in the refactor: the functions I propose to leave it here for now regarding this PR and the refactoring of discrete control. One thing left to do is a round of updating inline documentation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well done, it's more readable now. Besides, you coded even more subtle performance improvements.
core/src/callback.jl
Outdated
consisting of 'truth_values' for a DiscreteControl node. This truth_state then corresponds with a 'control_state', | ||
and nodes controlled by a DiscreteControl node have parameter values associated with this control_state. | ||
Apply the discrete control logic. There's somewhat of a complex structure: | ||
- Each DiscreteControl node can have one ore multiple compound variables it listens to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Each DiscreteControl node can have one ore multiple compound variables it listens to | |
- Each DiscreteControl node can have one or multiple compound variables it listens to |
Fixes #1512
Fixes #1539