I/O, memory and boot tuning for BigLinux.
Your laptop has one disk, and everybody wants it at the same time: the indexer, the package manager, the backup, the browser you just clicked on. Guess which one you are waiting for. This package makes sure it is the browser that gets through, picks a sane I/O scheduler for each disk, and stops the boot from redoing work pacman already did.
No GUI, no "AI-powered optimizer". Udev rules, systemd drop-ins, sysctl files, two short shell scripts and one small user service, deskbalance, that sleeps until a program starts or waits on the disk.
- What's in the box
- Install
- I/O scheduler
- Disk latency target for the session
- deskbalance
- Faster boot and login
- Memory and swap
- Btrfs housekeeping
- Odds and ends
- License
| File | What it does |
|---|---|
etc/udev/rules.d/60-ioschedulers.rules |
Picks the I/O scheduler per disk type; sets APM level 127 on rotational disks |
usr/lib/systemd/system/user.slice.d/50-io-latency.conf |
2 ms disk latency target for every login session |
usr/lib/systemd/system/user@.service.d/50-io-latency.conf |
Hands the io controller to the user manager |
usr/lib/systemd/user/{app,session}.slice.d/50-io-latency.conf |
The same target inside the session |
usr/bin/deskbalance, usr/lib/systemd/user/deskbalance.service, usr/lib/systemd/user.conf.d/50-deskbalance.conf |
Moves the job hogging the disk out of the way when a program starts or waits |
usr/lib/systemd/system/ldconfig.service.d/50-pacman.conf |
Rebuilds the linker cache at boot only when it is missing |
usr/lib/systemd/user/app-snap\x2duserd\x2dautostart@autostart.service.d/50-snapd.conf |
Runs snapd's login helper only where snapd is enabled |
etc/sysctl.d/91-memory.conf |
Swap, cache and writeback settings tuned for zram |
etc/sysctl.d/99-games-tweaks.conf |
Raises vm.max_map_count for games and turns off core dumps |
etc/modprobe.d/disable_snd_itco.conf |
Blacklists the Intel chipset watchdog and the PC speaker |
usr/bin/big-btrfs-fix-to-timeshift, usr/bin/big-btrfs-balance, usr/lib/systemd/system/big-btrfs-balance.service |
Resets the Btrfs default subvolume and runs a light balance after boot |
It ships in the BigLinux repositories:
sudo pacman -S auto-rules-iopacman reloads systemd on install, so the drop-ins are picked up right away; the ldconfig and snapd ones only matter at the next boot and login anyway. The scheduler rules and sysctl files live in /etc, where pacman's hooks do not look, so they apply at the next boot, or now with:
sudo udevadm trigger -c change && sudo sysctl --systemThe module blacklist waits for the reboot.
The install script enables big-btrfs-balance.service, and on every upgrade it rewrites one specific set of old Btrfs mount options in /etc/fstab (,space_cache=v2,autodefrag,compress-force=zstd:2,commit=15, with or without ,discard=async,ssd) to ,compress-force=zstd. Other fstab lines are left alone.
| Disk | Scheduler |
|---|---|
Rotational sd* (HDD, USB HDD included) |
BFQ |
Non-rotational sd* (SATA or USB SSD) |
Kyber |
mmcblk* (eMMC, SD card) |
BFQ |
nvme* |
Kyber |
Anything else (vd*, a 27th sd disk) keeps the kernel's default. Rotational disks also get hdparm -B 127, the highest APM level that still lets the disk spin down, provided hdparm is installed.
Every login session gets a 2 ms disk latency target on the disks holding / and /home (IODeviceLatencyTargetSec= on user.slice, and on app.slice and session.slice inside the user manager). While your programs miss it, the kernel's io.latency controller throttles everything around them:
system.sliceandmachine.slice: system services, containers and VMs, including work done on your behalf such as package updates and Docker builds;- the user's
background.slice: indexers and other polite-in-theory background jobs.
When the session does no I/O, nothing is throttled and everyone runs at full speed. It works under any scheduler, which is the point: ionice classes only act inside one cgroup under BFQ and not at all under Kyber, and IOWeight shares throughput under BFQ but promises nothing about latency.
2 ms is not magic, just measured: 1 ms is slower on HDDs and 3 ms is slower on NVMe.
Cold start in seconds, with a sequential reader hammering the disk in the background:
| Disk | Target | LibreOffice | Firefox |
|---|---|---|---|
| NVMe | none | 16.5–45 | 65–71 |
| NVMe | 2 ms | 1.5–2.4 | 2.1–3.2 |
| HDD | none | 104–111 | 112–132 |
| HDD | 2 ms | 37–40 | 74–80 |
For reference, alone they start in 0.8 and 1.4 s on the NVMe, and 10.6 and 16.5 s on the HDD. The HDD is still an HDD; this package has limits. The NVMe is a Ryzen 7 5700U laptop under Kyber, the HDD an i3-2375M under BFQ, with the page cache dropped before every start. One machine each, so your mileage will vary.
- The bill. On the NVMe, a reader that saturates the disk loses about a quarter of its rate while the session does any I/O. Buffered writes and random reads lose nothing measurable, and nothing measurable is lost on the HDD.
- Writeback. On an HDD the target does not help against buffered writes: their writeback happens outside its control.
- Your background counts too. All I/O from the session counts toward the
user.slicetarget,background.sliceincluded. While an indexer such as Baloo or localsearch misses the target, system services and VMs are throttled even if no program waits. The kernel adds each request's latency to every ancestor group with a target, andbackground.slicelives insideuser.slice. - Storage it can reach. The target was measured on plain partitions. systemd resolves a LUKS volume, or an LVM volume on a single disk, to the disk under it. On RAID, LVM over several disks, multi-device Btrfs or a network
/home, the target cannot reach the disks and does nothing.
user.slice holds every login, SSH and root included. On a server, that means an administrator's pacman -Syu would throttle the databases and VMs, which is rarely what anyone wants at 3 AM. Mask the three targets and reboot (the Delegate=io drop-in is harmless on its own):
sudo mkdir -p /etc/systemd/system/user.slice.d /etc/systemd/user/app.slice.d /etc/systemd/user/session.slice.d
sudo ln -s /dev/null /etc/systemd/system/user.slice.d/50-io-latency.conf
sudo ln -s /dev/null /etc/systemd/user/app.slice.d/50-io-latency.conf
sudo ln -s /dev/null /etc/systemd/user/session.slice.d/50-io-latency.confThe reboot is not optional: systemd drops the setting but leaves the kernel's target in place. Clearing it by hand (target=0 in io.latency) while it throttles leaves the throttled groups delayed until reboot anyway, because the kernel only clears the delay on the group that had the target.
The latency target throttles background.slice, but a copy started from your file manager or a terminal lands in app.slice, right next to the browser you are waiting for, where the target cannot tell them apart. deskbalance is a user service that fixes that:
- A program starts (a unit appears in
app.slice): for the 10 s after the last start (30 s at most, then none for 10 s),background.sliceand anyapp.sliceunit burning more than an eighth of the CPUs (at least one) share a quarter of the CPUs (at least one), so the new program keeps whole cores. - A job hogs the disk (at a start, or whenever
app.slicewaits on the disk): the busiest job of a program scope that has run for a minute or more (younger ones may still be loading) moves to a scope of its own inbackground.slice, under the latency target, with room for what it allocates plus 256 MB of page cache, and without swap, so a big copy cannot push your programs out of RAM. "Job" means process group: thecpyou started from a terminal moves, the terminal stays. - The job goes quiet for 10 s: it goes back to
app.slice, in a scope named after the one it left.
It blocks in inotify, a PSI trigger and a signalfd, so it does not wake up while nothing starts and nothing waits on the disk; while a job is moved, it looks at it once a second. On SIGTERM it lifts the CPU caps and puts every moved job back, and an instance that replaces a killed one takes over what it left. On a kernel without unprivileged PSI triggers, jobs move only when a program starts.
Measured on three laptops while a job in a program scope, started a minute earlier, copies a 3 GB file in a loop. The page cache is dropped before each start, and each program runs without a window and quits once ready; each laptop ran the programs it has installed. Times in seconds, lowest–highest of 3 starts on the NVMe laptops and 2 on the HDD one (with no load, one start where a single value is shown):
| No load | Copy, deskbalance off | Copy, deskbalance on | |
|---|---|---|---|
| Asus Vivobook M1503QA (Ryzen 5 5600H, 40 GB, NVMe) | |||
| LibreOffice | 0.6 | 2.1–4.3 | 0.9–4.2 |
| Firefox | 1.0 | 5.8–7.1 | 1.5–2.4 |
| GIMP | 2.6–2.7 | 15.3–17.1 | 3.7–4.3 |
| Blender | 1.7 | 11.2–17.0 | 2.4–3.7 |
| Acer Aspire A515-45 (Ryzen 7 5700U, 36 GB, NVMe) | |||
| LibreOffice | 0.6 | 4.2–5.3 | 0.7–4.0 |
| Firefox | 1.3 | 6.6–10.3 | 1.4–1.6 |
| GIMP | 2.9 | 10.1–12.7 | 3.1–4.8 |
| Blender | 2.0 | 8.3–8.9 | 3.3–3.9 |
| Xonotic server | 7.2 | 8.9–11.7 | 7.5–7.6 |
| Lenovo IdeaPad S400 (Core i3-2375M, 4 GB, HDD) | |||
| LibreOffice | 7.1–7.8 | 50.3–54.2 | 8.4–9.4 |
| Firefox | 19.3 | 77.5–87.9 | 21.5–25.2 |
| GIMP | 22.3–22.9 | 114.8–138.7 | 27.2–29.6 |
| Xonotic server | 15.7 | 57.7–64.2 | 19.7–20.4 |
On the NVMe laptops LibreOffice takes the high end when it is the first program to start once the copy is running: the copy then moves as LibreOffice starts, not before.
At rest it uses 0.6 MB of memory and no wakeups.
The fine print:
- A program that does the heavy I/O itself (a browser download, a game updater) moves as a whole, and comes back in a scope with
-deskbalance-…added to its name. - Any unit that appears in
app.slicecounts as a program starting, D-Bus-activated and timer-started ones included. - It does not know which window has the focus, so the program you are using can be the one capped for 10 s if it burns a lot of CPU while another one starts.
- On Plasma,
krunner, the polkit agent and the kscreen OSD run inbackground.slice, so they share that quarter of the CPUs for the 10 s after a program starts. - It reads each program's disk bytes from its cgroup, so the package turns on I/O accounting for user units (
DefaultIOAccounting=yes), which applies as soon as the install reloads each user's systemd instance. deskbalance itself starts when that instance starts again: at the next login with no other session of that user open and lingering off, or after a reboot. It caps busyapp.sliceunits only where something there already enabled the CPU controller; otherwise onlybackground.sliceis capped, and its log says so. - The package enables it for every user when it first installs it. To turn it off for everyone:
sudo systemctl --global disable deskbalance(upgrades leave that alone). Like enabling, that applies when each user's instance starts again; a deskbalance already running keeps going until then, or stops at once withsystemctl --user stop deskbalancein that user's session. For yourself only:systemctl --user mask --now deskbalance, since a user cannot disable what is enabled globally.
ldconfig.service rebuilds /etc/ld.so.cache at boot only when the cache is missing or empty, instead of at the first boot after every update. pacman already runs ldconfig after each transaction, so the boot was redoing it inside sysinit.target, where everything else waits: 8.8–9.9 s cold on an HDD, 0.5 s on an NVMe.
If you add libraries without pacman (a systemd-sysext image, a transaction killed halfway), run sudo ldconfig yourself. You were going to anyway, right?
snapd's XDG autostart (snap userd --autostart) only starts where snapd is enabled, that is, where /run/snapd.socket exists. With snapd turned off it still ran at every login and achieved nothing in 3.3–4.2 s cold on an HDD.
91-memory.conf assumes zram (or zswap) for swap:
| Setting | Value | Effect |
|---|---|---|
vm.page-cluster |
0 | Swap in one page at a time; zram compresses pages individually |
vm.swappiness |
30 | Prefer dropping cache over swapping |
vm.vfs_cache_pressure |
75 | Keep directory and inode caches a little longer |
vm.watermark_scale_factor |
150 | Wider gap between watermarks, so kswapd starts earlier |
vm.watermark_boost_factor |
15000 | Kernel default, written down: boost watermarks after fragmentation events |
vm.dirty_background_ratio / vm.dirty_ratio |
5 / 15 | Start writeback sooner |
vm.compaction_proactiveness |
20 | Kernel default, written down: background compaction level |
vm.extfrag_threshold |
300 | Compact rather than reclaim a bit sooner (default 500) |
vm.panic_on_oom |
0 | Kernel default, written down: let the OOM killer pick a victim instead of panicking |
vm.min_free_kbytes is not here: the pre-systemd-swap service sets it to 3% of RAM, up to 512 MB.
big-btrfs-balance.service runs once per boot, after multi-user.target:
big-btrfs-fix-to-timeshiftresets the default subvolume to the top-level one (the parent of@), which Timeshift expects.- Five minutes later,
big-btrfs-balancerunsbtrfs balancewith-dusage=50 -musage=50on/, and on/homeif it is a different filesystem, undernice 19andionice -c 3(the latter only counts on BFQ disks).
99-games-tweaks.confsetsvm.max_map_countto 2147483642, the value SteamOS uses for games, andkernel.core_patternto|/bin/false, which throws core dumps away, socoredumpctl(and any crash reporter built on it) has nothing to show. The99-prefix puts it last: sysctl files apply in name order, Manjaro's10-manjaro.confsetsvm.max_map_count, systemd's50-coredump.confsetskernel.core_pattern, and the later name wins. Ifsystemd-coredumpd.serviceis enabled, itssystemd-coredump-register.servicesetskernel.core_patternagain after sysctl runs, and core dumps come back.disable_snd_itco.confblacklistsiTCO_wdt, the Intel chipset watchdog, which nothing on the desktop arms, andpcspkr, the PC speaker, so nothing beeps at you from the motherboard.
GPL, as declared in pkgbuild/PKGBUILD.