Skip to content

perf: drive the rain rate sweep from a scheduled step rather than per frame (#186) - #187

Open
auroraxo wants to merge 1 commit into
peted-davis:mainfrom
auroraxo:rainfall-animation-redraw-rate
Open

auroraxo wants to merge 1 commit into
peted-davis:mainfrom
auroraxo:rainfall-animation-redraw-rate

Conversation

@auroraxo

Copy link
Copy Markdown

This is remedy 1 from #186, written as code so there is something concrete to accept, change or reject. The diagnosis in that issue is @mzac's; what I have added is the patch, a test suite for the panel, and a measurement.

What changes

animate_rain_rate() started a repeating Animation, which updates rain_rate_x once per frame and therefore redraws the window at the configured maxfps for the whole of the rainfall. The sweep takes 12 seconds, so none of that rate reaches the eye.

It is now a Clock.schedule_interval at 12 fps. The step is derived from dt rather than being a fixed increment, so the sweep still takes RAIN_RATE_PERIOD seconds if a frame is late or if you change RAIN_RATE_FPS. The three tunables are module constants at the top of the file:

RAIN_RATE_TRAVEL = -0.875
RAIN_RATE_PERIOD = 12
RAIN_RATE_FPS    = 12

Measured, not asserted

tests/measure_rain_redraw.py counts rain_rate_x dispatches over one full sweep at maxfps = 60, headless, on a synthetic clock:

window maxfps = 60, sweep duration = 12s, patch rate = 12 fps

Animation (current)            719 updates / 12s =  59.9 per second
schedule_interval (patch)      144 updates / 12s =  12.0 per second

719 -> 144: 80% fewer redraws per sweep

I have no Pi 3B, so I cannot give you the °C figure — that is the part only you or @mzac can confirm. What I can show is that the redraw count the temperature follows is down by a factor of five, and that it is now bounded by a constant in the file rather than by the window's frame rate.

Three smaller things in the same diff

  • start_rain_rate_animation / stop_rain_rate_animation replace the inline hasattr blocks, which were repeated in three places. Stopping now also resets rain_rate_x to 0, so the sweep restarts from the left edge rather than from wherever the rain happened to stop.
  • on_rain_rate_x is gone. The wrap at the end of travel is part of the step now, rather than a property callback reacting to a rounded comparison against -0.875.
  • In the "rain rate unavailable" branch, rain_rate_y is set to -1.00 unconditionally. Previously it was only reset if an animation happened to be running, so a panel that lost its observations while the rate was exactly 0 kept the last level it had.

Tests

tests/test_rainfall.py, 13 tests, all green:

$ python -m unittest discover tests
.............
Ran 13 tests in 0.039s

OK

Headless with the mock window and GL backends, a stubbed app and observation dict, and a clock whose time the test advances. No Pi, no display, no weather station, no network, no real time — the suite runs in 40 ms on an x86 laptop. They cover:

  • the state machine: no animation when the rate is - or 0, start when it rains, stop when it stops or becomes unavailable, repeated observations do not stack a second animation, stop is idempotent
  • the sweep: advances, wraps, never leaves [RAIN_RATE_TRAVEL, 0], and takes RAIN_RATE_PERIOD seconds at a different window fps
  • the redraw rate at maxfps 30, 60 and 120 — the regression guard for Rain-rate animation redraws at maxfps: +18°C CPU and thermal throttling on Pi 3B #186
  • the rain_rate_y level curve, which this change must not alter: monotonic in rain rate and saturating at the 50 mm/hr cut-off

The suite fails against main (the panel has no stop_rain_rate_animation), so it is testing the change rather than passing vacuously.

One test caught something worth knowing: at maxfps = 30 the step runs 121 times rather than 144, because a scheduled interval can only fire on a frame and 1/12 does not divide 1/30. That is harmless — the sweep keeps its period because the step reads dt — so the assertion is a bound rather than an equality, and the comment says why.

What I have not done

Remedies 2 and 3 from the issue — animating only while the panel is visible, and documenting a recommended maxfps for Pi 3-class hardware — are untouched. 2 needs the panel framework's visibility semantics, which are yours to decide; 3 needs hardware I do not have. Happy to follow up on either if this direction is right.

If you would rather have this as a smaller diff — just the animation change, no tests directory — say so and I will cut it down. I added the tests because a performance change with no regression guard is one that comes back.

The rain rate icon sweep was a repeating Kivy Animation, which updates
rain_rate_x once per frame and so redraws the window at the configured
maxfps for as long as the rain rate is non-zero. The sweep takes 12
seconds, so nothing about the visible motion needs that rate.

Drive it from Clock.schedule_interval at 12 fps instead. The step is
derived from the elapsed time rather than a fixed increment, so the sweep
still takes RAIN_RATE_PERIOD seconds whether a frame is late or the rate
is changed.

Measured headless over one 12 second sweep at maxfps = 60:
719 rain_rate_x updates before, 144 after - 80% fewer redraws.

Also:

- start/stop are now named methods rather than inline hasattr blocks, and
  stop resets rain_rate_x so the sweep restarts from the left edge
- on_rain_rate_x is gone: the wrap is now part of the step
- the "rain rate unavailable" branch sets rain_rate_y = -1.00
  unconditionally, rather than only when an animation happened to be
  running

tests/test_rainfall.py covers the animation state machine, the sweep
geometry and period, the redraw rate at maxfps 30/60/120, and the
rain_rate_y level curve, which this change must not alter. 13 tests,
headless with the mock window and GL backends and a synthetic clock: no
Pi, no display, no weather station. tests/measure_rain_redraw.py is the
measurement above, runnable.

Refs peted-davis#186
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant