Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
pixelpipe_cache_pressure.h
Go to the documentation of this file.
1/*
2 This file is part of Ansel,
3 Copyright (C) 2026 Aurélien PIERRE.
4
5 Ansel is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Ansel is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with Ansel. If not, see <http://www.gnu.org/licenses/>.
17*/
18
19#ifndef DT_CACHES_PIXELPIPE_CACHE_PRESSURE_H
20#define DT_CACHES_PIXELPIPE_CACHE_PRESSURE_H
21
22/* How much the pixelpipe cache may hold while the kernel reports memory pressure, and what makes
23 * it hold that.
24 *
25 * A cache is one module and memory management is another: pixelpipe_cache.c holds entries and
26 * evicts them, this decides when and how much, and system/memory_pressure.c is the only file that
27 * knows what a kernel counter looks like. So nothing below reads an entry and nothing below is
28 * written twice for two platforms; the cache passes a sink -- how much it holds, and what it
29 * costs to give some back -- and is told what to shed.
30 *
31 * The first half is pure arithmetic, separated the way develop/pipe_cache_policy.h is: those are
32 * decisions nothing else can observe -- they change no pixel and no hash, only how much cache
33 * survives a stalling machine -- so the only way to hold them to their rules is to state them
34 * where a test can call them. tests/unittests/test_pipe_cache_pressure.c pins both defects this
35 * policy was written around, each found on a live run:
36 *
37 * - a ceiling that climbed straight back to the plan brought the stall back within half a
38 * minute, five times in five minutes, once at 49% -- one point under systemd-oomd's limit;
39 * - a stall while the cache still held nothing (four kernel wake-ups in the first 12 s of a
40 * run) recorded a pressure mark of 0 and pinned the budget at the floor, where it stayed
41 * for the next quarter of an hour. */
42
44
45#include <glib.h>
46#include <stddef.h>
47#include <stdint.h> // SIZE_MAX
48
49/* The window the stall share is measured over, and what the kernel's own trigger is armed at.
50 * Unprivileged PSI triggers require a multiple of 2 s. */
51#define DT_PIXELPIPE_CACHE_PSI_WINDOW_US ((gint64)2 * 1000 * 1000)
52/* Share of a window spent fully stalled that makes the cache give memory back. systemd-oomd
53 * kills at 50% over 20 s, so this leaves room to act before it does. */
54#define DT_PIXELPIPE_CACHE_PSI_SHED 0.10
55/* ... and under which the budget may start climbing back. */
56#define DT_PIXELPIPE_CACHE_PSI_CALM 0.02
57
58/* "Hold what you have": the answer of dt_pixelpipe_cache_pressure_shed_target() when the cache
59 * is not what the machine is short of. Distinct from a target of 0, which means "shed it all". */
60#define DT_PIXELPIPE_CACHE_PRESSURE_KEEP SIZE_MAX
61
73
77{
78 return p->plan / 8;
79}
80
87{
88 return p->plan / 7 * 8 + 8;
89}
90
92{
93 p->plan = plan;
94 p->ceiling = plan;
96}
97
100{
101 return MIN(p->plan, p->ceiling);
102}
103
106{
107 return MIN(p->plan, p->mark / 8 * 7);
108}
109
113 const size_t current)
114{
115 return MAX(dt_pixelpipe_cache_pressure_budget(p), current);
116}
117
123 const double share, const size_t current)
124{
127
128 const size_t step = MAX(current / ((share >= 3. * DT_PIXELPIPE_CACHE_PSI_SHED) ? 2 : 4),
129 (size_t)256 * 1024 * 1024);
130 return (current > step) ? current - step : 0;
131}
132
138 const size_t before, const size_t after)
139{
140 p->mark = before;
141 p->ceiling = MIN(p->ceiling, MAX(after, dt_pixelpipe_cache_pressure_floor(p)));
142}
143
147{
148 p->mark = MIN(dt_pixelpipe_cache_pressure_mark_top(p), p->mark + p->plan / 1024);
149
150 const size_t cap = dt_pixelpipe_cache_pressure_cap(p);
151 if(p->ceiling >= cap) return FALSE;
152
153 p->ceiling = MIN(cap, p->ceiling + p->plan / 32);
154 return TRUE;
155}
156
157/* The cache's reaction to what the kernel reports, implemented in pixelpipe_cache_pressure.c:
158 * the PSI counters two reads turn into a stall share, the budget above that share steers, and
159 * the watcher thread the kernel wakes. The cache holds one of these and asks it; it holds no
160 * lock and no cache entry of its own, so it can say how much must go without knowing what the
161 * cache is made of. */
163{
164 /* Guarded by whatever guards the cache this steers: the caller takes its own lock around every
165 * function below except the two that start and stop the watcher. */
168 gint64 time_us;
171 /* NOT guarded: written before the watcher's thread starts and after it has joined. */
174
175/* The other side of the conversation: the monitor says how much must go, this is what going
176 * costs. Both run under the caller's lock, since the monitor is called under it. */
178{
180 size_t (*held)(void *user);
184 size_t (*shed)(void *user, size_t target, size_t *given_back);
185 void *user;
187
190
191/* Have the kernel wake us rather than wait for a window of our own to close: `wake(user)` runs on
192 * the watcher's thread and must take whatever guards the monitor before calling
193 * dt_pixelpipe_cache_pressure_triggered(). A platform without PSI triggers arms nothing, which
194 * leaves the measured windows below as the only reaction. */
196 void (*wake)(void *user), void *user);
199
204
208
209#endif // DT_CACHES_PIXELPIPE_CACHE_PRESSURE_H
210
211// clang-format off
212// modelines: These editor modelines have been set for all relevant files by tools/update_modelines.py
213// vim: shiftwidth=2 expandtab tabstop=2 cindent
214// kate: tab-indents: off; indent-width 2; replace-tabs on; indent-mode cstyle; remove-trailing-spaces modified;
215// clang-format on
#define TRUE
Definition ashift_lsd.c:162
#define FALSE
Definition ashift_lsd.c:158
#define m
Definition basecurve.c:283
#define DT_MEMORY_PRESSURE_MAX_LEVELS
#define DT_PIXELPIPE_CACHE_PRESSURE_KEEP
static size_t dt_pixelpipe_cache_pressure_cap(const dt_pixelpipe_cache_pressure_t *p)
static size_t dt_pixelpipe_cache_pressure_shed_target(const dt_pixelpipe_cache_pressure_t *p, const double share, const size_t current)
#define DT_PIXELPIPE_CACHE_PSI_SHED
void dt_pixelpipe_cache_pressure_triggered(dt_pixelpipe_cache_pressure_monitor_t *m, const dt_pixelpipe_cache_pressure_sink_t *sink)
static void dt_pixelpipe_cache_pressure_after_shed(dt_pixelpipe_cache_pressure_t *p, const size_t before, const size_t after)
void dt_pixelpipe_cache_pressure_monitor_init(dt_pixelpipe_cache_pressure_monitor_t *m, size_t plan)
static gboolean dt_pixelpipe_cache_pressure_relax(dt_pixelpipe_cache_pressure_t *p)
static void dt_pixelpipe_cache_pressure_init(dt_pixelpipe_cache_pressure_t *p, const size_t plan)
static size_t dt_pixelpipe_cache_pressure_reported(const dt_pixelpipe_cache_pressure_t *p, const size_t current)
static size_t dt_pixelpipe_cache_pressure_floor(const dt_pixelpipe_cache_pressure_t *p)
static size_t dt_pixelpipe_cache_pressure_mark_top(const dt_pixelpipe_cache_pressure_t *p)
void dt_pixelpipe_cache_pressure_watch_start(dt_pixelpipe_cache_pressure_monitor_t *m, void(*wake)(void *user), void *user)
size_t dt_pixelpipe_cache_pressure_react(dt_pixelpipe_cache_pressure_monitor_t *m, const dt_pixelpipe_cache_pressure_sink_t *sink)
void dt_pixelpipe_cache_pressure_watch_stop(dt_pixelpipe_cache_pressure_monitor_t *m)
static size_t dt_pixelpipe_cache_pressure_budget(const dt_pixelpipe_cache_pressure_t *p)
unsigned __int64 uint64_t
Definition strptime.c:75
uint64_t totals[DT_MEMORY_PRESSURE_MAX_LEVELS]
size_t(* shed)(void *user, size_t target, size_t *given_back)
#define MIN(a, b)
Definition thinplate.c:32
#define MAX(a, b)
Definition thinplate.c:29