|
4 | 4 | from typing import TYPE_CHECKING |
5 | 5 |
|
6 | 6 | import numpy as np |
7 | | -from numba import njit, int64, float64 # type: ignore[import-untyped] |
8 | | -from numba.experimental import jitclass # type: ignore[import-untyped] |
| 7 | +from numba import njit # type: ignore[import-untyped] |
9 | 8 |
|
10 | 9 | from parol6 import config as cfg |
11 | 10 |
|
|
21 | 20 |
|
22 | 21 |
|
23 | 22 | # ============================================================================= |
24 | | -# Numba-accelerated statistics functions |
| 23 | +# Numba-accelerated statistics functions (cached to disk) |
25 | 24 | # ============================================================================= |
26 | 25 |
|
27 | 26 |
|
@@ -137,24 +136,27 @@ def _compute_loop_stats( |
137 | 136 |
|
138 | 137 |
|
139 | 138 | # ============================================================================= |
140 | | -# PhaseMetrics - jitclass for phase timing |
| 139 | +# PhaseMetrics - regular Python class (no jitclass overhead) |
141 | 140 | # ============================================================================= |
142 | 141 |
|
143 | | -_phase_metrics_spec = [ |
144 | | - ("_buffer", float64[:]), |
145 | | - ("_scratch", float64[:]), |
146 | | - ("_buffer_idx", int64), |
147 | | - ("_buffer_count", int64), |
148 | | - ("last_s", float64), |
149 | | - ("mean_s", float64), |
150 | | - ("max_s", float64), |
151 | | - ("p99_s", float64), |
152 | | -] |
153 | 142 |
|
154 | | - |
155 | | -@jitclass(_phase_metrics_spec) |
156 | 143 | class PhaseMetrics: |
157 | | - """Rolling statistics for a single phase (numba jitclass).""" |
| 144 | + """Rolling statistics for a single phase. |
| 145 | +
|
| 146 | + Uses pre-allocated numpy arrays and calls @njit helper functions |
| 147 | + for the heavy computation. |
| 148 | + """ |
| 149 | + |
| 150 | + __slots__ = ( |
| 151 | + "_buffer", |
| 152 | + "_scratch", |
| 153 | + "_buffer_idx", |
| 154 | + "_buffer_count", |
| 155 | + "last_s", |
| 156 | + "mean_s", |
| 157 | + "max_s", |
| 158 | + "p99_s", |
| 159 | + ) |
158 | 160 |
|
159 | 161 | def __init__(self) -> None: |
160 | 162 | self._buffer = np.zeros(BUFFER_SIZE, dtype=np.float64) |
@@ -268,45 +270,42 @@ def __exit__(self, *args: object) -> None: |
268 | 270 |
|
269 | 271 |
|
270 | 272 | # ============================================================================= |
271 | | -# LoopMetrics - jitclass for loop timing metrics |
| 273 | +# LoopMetrics |
272 | 274 | # ============================================================================= |
273 | 275 |
|
274 | | -_loop_metrics_spec = [ |
275 | | - # Counters |
276 | | - ("loop_count", int64), |
277 | | - ("overrun_count", int64), |
278 | | - # Rolling stats |
279 | | - ("mean_period_s", float64), |
280 | | - ("std_period_s", float64), |
281 | | - ("min_period_s", float64), |
282 | | - ("max_period_s", float64), |
283 | | - ("p95_period_s", float64), |
284 | | - ("p99_period_s", float64), |
285 | | - # Circular buffer |
286 | | - ("_buffer", float64[:]), |
287 | | - ("_scratch", float64[:]), |
288 | | - ("_buffer_idx", int64), |
289 | | - ("_buffer_count", int64), |
290 | | - # Timing control |
291 | | - ("_target_period_s", float64), |
292 | | - ("_prev_time", float64), |
293 | | - ("_last_log_time", float64), |
294 | | - ("_last_warn_time", float64), |
295 | | - ("_stats_interval", int64), |
296 | | - # Startup grace period |
297 | | - ("_start_time", float64), |
298 | | - ("_grace_period_s", float64), |
299 | | -] |
300 | | - |
301 | | - |
302 | | -@jitclass(_loop_metrics_spec) |
| 276 | + |
303 | 277 | class LoopMetrics: |
304 | | - """Metrics tracked by the loop timer with rolling statistics (numba jitclass). |
| 278 | + """Metrics tracked by the loop timer with rolling statistics. |
305 | 279 |
|
306 | 280 | Provides unified timing, logging, and degradation checking across subsystems. |
307 | 281 | Use configure() to set target period, then call tick() each iteration. |
| 282 | +
|
| 283 | + Uses pre-allocated numpy arrays and calls @njit helper functions |
| 284 | + for the heavy computation. |
308 | 285 | """ |
309 | 286 |
|
| 287 | + __slots__ = ( |
| 288 | + "loop_count", |
| 289 | + "overrun_count", |
| 290 | + "mean_period_s", |
| 291 | + "std_period_s", |
| 292 | + "min_period_s", |
| 293 | + "max_period_s", |
| 294 | + "p95_period_s", |
| 295 | + "p99_period_s", |
| 296 | + "_buffer", |
| 297 | + "_scratch", |
| 298 | + "_buffer_idx", |
| 299 | + "_buffer_count", |
| 300 | + "_target_period_s", |
| 301 | + "_prev_time", |
| 302 | + "_last_log_time", |
| 303 | + "_last_warn_time", |
| 304 | + "_stats_interval", |
| 305 | + "_start_time", |
| 306 | + "_grace_period_s", |
| 307 | + ) |
| 308 | + |
310 | 309 | def __init__(self) -> None: |
311 | 310 | self.loop_count = 0 |
312 | 311 | self.overrun_count = 0 |
@@ -407,8 +406,7 @@ def compute_stats(self) -> None: |
407 | 406 |
|
408 | 407 | def reset_stats(self) -> None: |
409 | 408 | """Reset rolling statistics (keeps loop_count and overrun_count).""" |
410 | | - for i in range(BUFFER_SIZE): |
411 | | - self._buffer[i] = 0.0 |
| 409 | + self._buffer.fill(0.0) |
412 | 410 | self._buffer_idx = 0 |
413 | 411 | self._buffer_count = 0 |
414 | 412 | self.mean_period_s = 0.0 |
|
0 commit comments