Re: memcg stats flushing (WAS "Re: [PATCH] fuse: disable default bdi strictlimiting")
Joanne Koong <[email protected]>
| Newsgroups | dev.linux.lists.fuse-devel,org.kernel.vger.cgroups,org.kernel.vger.linux-fsdevel,org.kvack.linux-mm |
|---|---|
| Message-ID | <CAJnrk1YifN22mA5PFRVrh3m_ygxax6x4_8nraG2hPqCzyzBj7A@mail.gmail.com> |
On Thu, Aug 6, 2026 at 2:31 PM Yosry Ahmed <[email protected]> wrote: > > On Thu, Aug 6, 2026 at 2:04 PM Joanne Koong <[email protected]> wrote: > > > > I'm not too familiar with memcg or vmstat so apologies if this is a > > naive question, but does it make sense for memcg to just do a similar > > approach to what vmstat does? As I understand it, mod_lruvec_state() > > updates the node counter and the memcg counter (if memcg applies), > > where for updating the node counter, mod_node_state() does some > > batching where it updates the global counter for that stat only once > > it crosses some threshold (pcp->stat_threshold). Could memcg keep its > > per-CPU counter as the accumulator but once it reaches some threshold, > > it then propagates it up the parent/ancestors into a per-memcg atomic > > global counter? The atomic would then only have to be touched once per > > batch than on every update, which might eliminate the overhead of the > > per-memcg atomic you saw previously? It seems like this would get rid > > of needing to do any flushing altogether, as we could just read that > > global counter directly. > > The memcg stats perform similar per-CPU counting and thresholding. The > main difference from vmstat AFAICT is the cgroup hierarchy. In vmstat > we have per-CPU counters and a global atomic counter for each stat. > For memcg, we have the same for each cgroup, then we also have a > cgroup tree. When we read the stats of a cgroup, we usually want the > hierarchical stats including all of its children, so we can't just > read the global counter. > > We use rstat, a cgroup framework that keeps track of which cgroups > have updates on which CPUs, and then memcg has thresholding logic on > top to only flush if the number of pending updates exceeds a > threshold. Essentially, if the magnitude updates on a CPU exceeds > MEMCG_CHARGE_BATCH, we add it to a global per-memcg counter. We only > flush if that global per-memcg counter has > NR_CPUS * > MEMCG_CHARGE_BATCH updates. So in theory I think we tolerate up to 2 * > NR_CPUS * MEMCG_CHARGE_BATCH of stale stats (combined for all the > stats). > > See memcg_rstat_updated() and memcg_vmstats_needs_flush() for this logic. > > Looking at this code again, it made me realize that we already iterate > the per-CPU stats_updates on every update to increment the number of > pending stats on this CPU. I wonder if we can replace this with > actually updating the stat on each parent, then the flush path only > needs to accumulate CPU counters. Or maybe as you mention, we update > the global per-memcg counter when updates on a CPU exceed a threshold, > and forgo the rstat flushing logic completely. > I can run some experiments and see if updating the global per-memcg counter per batch and forgoing flushing could be a viable solution. I can use my setup to test it for writeback, but for the zswap use case, what's the best way to test if the changes are too expensive? Is there a benchmark program you're running? For updating the stat on each parent and having the flush path only accumulate CPU counters, I think this means we would have to unconditionally walk the ancestors + update its counters on every update, as we wouldn't be able to early break if the flushable threshold was already exceeded. I don't think we could replace the stats_update update, as it seems like readers still need some way of knowing when to flush. It seems like this could be a nontrivial performance hit, but maybe it's a non-factor in reality. If you think this would be useful to get benchmark numbers on, I can run some experiments on this too. Thanks, Joanne