Skip to content

os, dix, mi, render: optimize RAM consumption in background when idle - #3961

Open
GermanAizek wants to merge 1 commit into
X11Libre:masterfrom
GermanAizek:opt-ram-trim-alloc
Open

GermanAizek wants to merge 1 commit into
X11Libre:masterfrom
GermanAizek:opt-ram-trim-alloc

Conversation

@GermanAizek

@GermanAizek GermanAizek commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

@metux, I’d like to raise a pressing issue about optimizing memory using malloc_trim by default (https://man7.org/linux/man-pages/man3/malloc_trim.3.html)

malloc_trim function first appeared in 1996 as part of the ptmalloc memory allocator
(Wolfram Glogger’s multithreaded extension of Doug Lea’s dlmalloc).

The function is declared with 1997 year in <malloc.h> and is a GNU-specific extension.
Its purpose is to attempt to release unused memory from the top of the heap (via sbrk) or from arenas
back to the operating system.

Regressions on x11perf not found.

The prices of RAM and flash memory continue to rise and are once again breaking records.

https://nl.pcpartpicker.com/trends/price/memory/#ram.ddr5.6000.2x16384

image

Solution for low RAM specs from Meta

In this regard, to optimize costs for purchasing RAM, Meta has decided to optimize compression instead of using ZRAM and ZSWAP.

I think we also need to value every user megabyte, because it’s unclear when prices for components will start to fall, and I hope you agree that x11libre also supports reuse electronic waste for ECO environmental purposes.

My benchmarks

image

Changes:

Implement background memory trimming and heap compaction when the X server
is idle, without changing the default glibc mem allocator or breaking ABI:

  • Allocator tuning (os/osinit.c):
    • Set M_TRIM_THRESHOLD (128 KB) and M_ARENA_MAX (2) at startup.
    • Explicitly pin M_MMAP_THRESHOLD (128 KB) to prevent dynamic threshold
      escalation that causes heap retention and fragmentation.
  • Background idle trim timer (os/WaitFor.c):
    • Trigger an idle trim callback after 5 seconds of inactivity.
    • Disarm on active client requests (0% active rendering overhead).
    • Re-arm periodically (every 60s) and hook into screen saver / DPMS events.
  • Buffer and cache reclamation:
    • Free empty input (oc->input) and output (oc->output) buffers for idle
      clients, along with AvailableInput and buffer pools (os/io.c).
    • Purge font pattern caches on idle (dix/dixfonts.c).
    • Downsize client resource hash tables back to INITBUCKETS (dix/resource.c).
    • Shrink the mi event queue back to its initial size (mi/mieq.c).
    • Compact global glyph hash tables and clear tombstones (Xext/render/glyph.c).
    • Release the byte-swapped event staging buffer (dix/events.c).
  • Clean non-core library page reclamation (os/WaitFor.c):
    • Issue madvise(MADV_DONTNEED) exclusively on clean executable (r-xp)
      and offset-0 rodata mappings of heavy non-core libraries (libLLVM,
      libz3, libstdc++, libxml2, and Mesa DRI drivers).
    • Strictly preserve writable and GNU_RELRO segments to protect relocations.
    • Invoke malloc_trim(0) to return heap pages to the kernel.

@GermanAizek
GermanAizek force-pushed the opt-ram-trim-alloc branch 3 times, most recently from ab524e9 to 2968091 Compare October 14, 2026 00:35
Comment thread .github/workflows/build-xserver.yml Outdated
Comment on lines 349 to 357
if [ "${{ github.repository }}" = "X11Libre/xserver" ] && [ "${{ github.event_name }}" != "pull_request" ]; then
docker push "$img:$tag"
if [ "${{ github.ref }}" = "refs/heads/master" ]; then
docker tag "$img:$tag" "$img:latest"
docker push "$img:latest"
fi
else
echo "Skipping docker push: write permissions to GHCR unavailable on pull requests or forks"
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks unrelated to this PR - should be slit out to separate one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I accidentally pulled in local changes

@metux
metux requested a review from a team October 15, 2026 12:03
@metux
metux force-pushed the opt-ram-trim-alloc branch from 2968091 to 81f1357 Compare October 15, 2026 12:04
Comment thread dix/events.c Outdated
Comment on lines +6148 to +6157
void
dixTrimEvents(void)
{
if (swapEvent) {
free(swapEvent);
swapEvent = NULL;
swapEventLen = 0;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we don't even need to keep that buffer global. it's not used often and could be allocated / freed when needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix it 1b5cf6a

@github-actions

Copy link
Copy Markdown

Merge Conflict found

@metux

metux commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

@GermanAizek has conflicts that need to be resolved.

When the X server runs for extended periods or handles bursty client
workloads, significant memory bloat is retained across heap arenas,
unshrunk resource tables, event queues, client I/O buffers, and dormant
shared libraries even after all client resources are destroyed.

Implement comprehensive background idle memory reclamation without
changing memory allocators and preserving full ABI compatibility:

- Startup allocator tuning:
  * Configure M_TRIM_THRESHOLD (128 KB) and M_ARENA_MAX (2).
  * Configure M_MMAP_THRESHOLD (128 KB) to prevent runaway dynamic
    threshold escalation that causes heap fragmentation.

- Idle background trim timer:
  * Schedule an ABI-safe idle callback after 5 seconds of inactivity.
  * Disarm the timer on incoming client traffic so active workloads
    suffer zero overhead or latency penalty.
  * Re-arm periodically (every 60 seconds) during sustained idle states,
    and trigger trim on screensaver / DPMS power-saving events.

- Buffer and cache reclamation:
  * Reclaim idle client input (oc->input) and output (oc->output) buffers,
    along with AvailableInput and cached free buffer pools.
  * Empty dix font pattern caches (patternCache and fontPatternCache).
  * Downsize client resource bucket tables back to INITBUCKETS when
    client resource counts shrink (preserving serverClient index 0).
  * Compact mi input event queue (miEventQueue) back to initial size.
  * Compact global glyph hash tables and clear deleted glyph tombstones.

- Clean non-core library page reclamation:
  * Inspect /proc/self/maps on Linux and issue madvise(MADV_DONTNEED) on
    purely clean file-backed segments (executable r-xp code and file-offset
    zero rodata) of heavy dormant libraries (libLLVM, libz3, libstdc++,
    libxml2, and Mesa DRI drivers).
  * Strictly avoid writable or GNU_RELRO segments to preserve copy-on-write
    dynamic relocations.
  * Invoke malloc_trim(0) to release freed heap pages to the kernel.

- Robust listener socket binding:
  * In _XSERVTransSocketUNIXCreateListener(), attempt direct bind() first
    and only inspect/unlink stale files on EADDRINUSE to prevent race
    conditions between concurrent servers under -displayfd.

- CI compatibility:
  * Guard M_ARENA_MAX and HAVE_MALLOPT for Cygwin.

Signed-off-by: Herman Semenoff <GermanAizek@yandex.ru>
@GermanAizek

Copy link
Copy Markdown
Contributor Author

@GermanAizek has conflicts that need to be resolved.

Fix it 1b5cf6a

@metux
metux force-pushed the master branch 5 times, most recently from eae2c21 to 483a172 Compare October 30, 2026 11:57
@GermanAizek

Copy link
Copy Markdown
Contributor Author

@metux Haven’t you reviewed this PR yet? I specifically made all the other PRs drafts so that I could finish the work with the active ones first.

@GermanAizek

Copy link
Copy Markdown
Contributor Author

@metux origin story malloc_trim() and algorithm

In Unix systems, memory trimming is already available and complies with POSIX standards — it has been so since the end 90s. It’s just that many developers have forgotten about this technology. Back then, the price of RAM per 1 MB was high, just like it is now. That’s why they came up with the idea of manually freeing up memory earlier call signal to OS memallocator and using it for other processes without losing performance — but this requires manual work from programmer.

Comment thread os/Xtranssock.c
* Don't blindly unlink a pre-existing socket file: it may belong to a
* server that's still running (see unix_socket_is_live()). Only clean
* up and rebind if nobody answers there.
* Try to bind directly without unlinking first. If the socket file does

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should go to other commit, or even better other PR.

Comment thread os/WaitFor.c
#endif

static CARD32
IdleTrimCallback(OsTimerPtr timer, CARD32 now, void *arg)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid there are chance with timer callback like this to be source of unexplained latency spikes.

I don't know how it should be approached, one of suggestion to hook it on new opcode in xtest extension (the only few calls defined) and allow to be called manually

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.

3 participants