Trajectory is O(N^2) per run in append and find; dominates runtime once trials are cheap
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 99
- Forks
- 62
- Avg merge
- 6d 12h
- Merged PRs (30d)
- 17
Description
Summary
Trajectory is O(N²) in two independent places, both hit once per step. It is invisible when a trial costs tens of seconds, and it dominates completely once trials get cheap — which is the point of a surrogate/proxy model.
The two paths
append rebuilds the whole frame every step (src/cloudai/configurator/trajectory.py:128):
self._dataframe = lazy.pd.concat([self._dataframe, row_frame], ignore_index=True).astype(object)
A full copy plus an .astype(object) over every accumulated row, per append.
find scans with iterrows() every step (src/cloudai/configurator/trajectory.py:144-147). It is called from CloudAIGymEnv.get_cached_trajectory_result, which runs on every step to decide whether a configuration can be served from cache.
Measured
find alone, worst case (no match), pandas row loop over a 4-column frame:
| trials | one find |
N finds over a run |
|---|---|---|
| 160 | 4.6 ms | 0.7 s |
| 1,000 | 19 ms | 19 s |
| 10,000 | 183 ms | 30 min |
Why it matters now
The cost only shows up relative to the trial cost:
- Real simulator, ~66 s/trial. 10,000 trials is ~183 hours. 30 minutes of bookkeeping is 0.3% — correctly ignorable, and presumably why this has never surfaced.
- Surrogate model, ~1 ms/trial. 10,000 trials is ~10 seconds of actual prediction against ~30 minutes of trajectory bookkeeping. The framework overhead is roughly 180x the work being done.
We hit the second case: a proxy model stands in for the simulator specifically so that DSE and RL runs can afford tens of thousands of trials. Making trials ~60,000x cheaper moved the bottleneck into Trajectory.
Suggested direction
Both are local to Trajectory and do not change its public surface:
find— maintain a dict keyed on the flattened(action, env_params)criteria, so a cache probe is a hash lookup rather than a scan. The method already computes exactly that key tuple incriteria.append— accumulate rows in a list and materialiseself._dataframeon demand (property, memoised, invalidated on append). That also removes the per-step.astype(object)over the full history.
The CSV write per append is fine and worth keeping — it is what makes a run recoverable, and it is what consumers read.
Happy to put up a PR if the direction is agreeable.
Found while reviewing a plugin repo's RL stack; measurements are a microbenchmark of iterrows at the relevant sizes, not an end-to-end profile.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in src/cloudai/configurator/trajectory.py at append (around line 128) and find (around lines 144-147), then trace CloudAIGymEnv.get_cached_trajectory_result to understand cache probes. Preserve the public surface, keep CSV recovery writes, and verify that repeated appends and cache lookups return the same results with substantially less per-step work.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- pandas, python
- Domain
- performance
- Issue type
- Refactor
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100