Before spending two hours tuning kernel parameters, you need to accept something many Arma 3 performance guides dance around: the game's engine is the primary constraint, and no amount of Linux tuning overcomes a fundamental architectural limitation. As measured by the community across years of profiling sessions, Arma 3's Real Virtuality 4 engine historically places 90 to 95 percent of its computational work on a single thread. The cluster-community wiki states it directly: "Arma 3 puts 90-95% of all work on one thread, so what you need the most to get good FPS is a CPU with high IPC and a high clockspeed." [source: cluster-community Arma 3 Performance Guide -- community-aggregated, pre-2.20]
Bohemia Interactive shipped update 2.20 -- delivered through the Profiling branch starting in late 2024 and documented in their OPREP "Performance Optimizations in 2.20" -- which substantially overhauled the engine's multithreading code. The developer statement is explicit: "We did not 'add' multithreading in update 2.20. The RV engine has had multithreading since Arma 2. But we did overhaul all of our multithreading code and made some optimizations." [source: Bohemia Interactive OPREP, dev.arma3.com] The 2.20 changes moved explosion damage visibility checks, listbox sorting, and physics simulation for high-density object scenarios to parallel execution. The primary result is fewer and shorter frame time spikes rather than a large jump in average FPS -- the OPREP frames it as making the game feel smoother, not necessarily faster in raw terms. Community YAAB benchmark submissions on the Profiling branch with the updated mimalloc allocator reported gains of 40-55% in AI-intensive scenarios, with the gains compounding when combined. The practical implication for this guide: single-thread IPC and cache size remain the dominant hardware variables, but the secondary threads now absorb enough meaningful work that starving them (via aggressive affinity pinning or restrictive taskset use) carries a higher penalty than it did before 2.20. Read the Gamemode and CPU Affinity section accordingly.
That context does not make this guide useless. It makes it more important. When you are constrained at the engine level, every microsecond of OS overhead, every cache miss, every page fault, every synchronization stall shows up directly as dropped frames or stutters. On Linux under Proton, you are running a Windows game through a compatibility translation layer that introduces its own overhead, and the Linux kernel's defaults are tuned for general-purpose workloads -- not for extracting maximum performance from a real-time simulation engine now actively distributing work across threads.
Linux-specific tuning can meaningfully recover performance that Proton and default kernel settings leave on the table -- but only if you understand why each tweak works at the system level. That is what this guide is.
Understanding the Stack
When you click Play on a modern Linux system, you are not running Arma 3. You are running a layered environment. Reading from the top (application) down to hardware:
- The Arma 3 Windows PE executable running inside the Wine prefix -- at the top of the call chain
- DXVK -- a Vulkan-based implementation of Direct3D 8 through 11, loaded as DLL overrides inside the Wine prefix. It intercepts the game's DirectX 11 calls before they reach Wine's own graphics layer, translating them into Vulkan API calls. The Proton DeepWiki architecture notes explicitly: "Direct3D calls never reach Wine; DXVK/vkd3d-proton intercept them and translate to Vulkan." [source: ValveSoftware/Proton DeepWiki]
- Wine's
ntdll,kernel32, and other Windows API emulation layers -- beneath the application, providing the Windows runtime environment. Per the Wine architecture: a Windows application calls high-level Windows API DLLs (kernel32, user32, etc.), which in turn call the lower-level NT API implemented in ntdll. [source: wine-mirror DeepWiki, Windows API Implementation] - Proton -- Valve's Wine-based compatibility layer that bundles all the above components (Wine, DXVK, FSync patches, Steam integration) and manages the Wine prefix lifecycle
- The Steam client (native Linux) -- launches Proton and passes game arguments
- Below all of that: your Linux kernel, your Mesa or NVIDIA driver, and your hardware
Each layer introduces latency and translation overhead. Bohemia Interactive itself acknowledged the situation directly: the native Linux port was discontinued because, as they put it, "the Proton-based solution became very capable, thanks to its developers and contributors." [source: Bohemia Interactive, dev.arma3.com/ports] Proton is now the officially supported Linux path. On ProtonDB, Arma 3 holds a "Gold" rating. The Bohemia Interactive community wiki confirms: "On the ProtonDB page, Arma 3 is ranked 'Gold' in compatibility. This means the game requires tweaking on Linux to run smoothly." [source: community.bistudio.com]
Understanding this stack tells you where to apply pressure. You are not just tuning a game -- you are tuning a runtime environment that lives between a Windows application and a Linux kernel. Critically, DXVK operates as DLL overrides within the Wine prefix -- the game never calls Wine's built-in graphics layer for its Direct3D work; DXVK intercepts those calls first. Wine's API layers handle everything else: threading, memory management, filesystem, and synchronization primitives. This is why FSync (which reduces Wine synchronization overhead) and DXVK (which handles graphics translation) are separate, complementary optimizations that address entirely different parts of the stack.
Layer 1: Proton Version and Launch Configuration
Why Proton Version Matters
Not all Proton versions behave identically for Arma 3. The Bohemia Interactive community wiki notes that Proton Experimental receives daily changes and regressions are common. A stable numbered release gives you a reproducible baseline. Many players also get better results from Proton-GE (GloriousEggroll builds), a community-maintained fork with additional patches targeting game compatibility. Install it via ProtonUp-Qt, which manages version switching cleanly.
The Bohemia Interactive community wiki explicitly warns: "Avoid Proton Experimental as that can change overtime." [source: community.bistudio.com, Arma 3: Play on Linux] Note: the BI wiki's specific version recommendation (currently citing Proton 7.0-1) is frequently out of date since the wiki is not updated on Proton's release cadence -- treat their version number as a placeholder, not a current recommendation. As a starting point, use the latest stable numbered GE-Proton release (from the GloriousEggroll GitHub releases page), then benchmark with YAAB against a stable official Proton release such as Proton 9.0 to determine which performs better for your specific hardware. Proton Experimental is useful for diagnosing regressions but should not be your daily driver for Arma 3. Always check the Arma 3 ProtonDB page (protondb.com/app/107410) for current community reports on which versions are working -- ProtonDB ratings are community-aggregated and shift over time, so the current reported rating at time of reading may differ from when this guide was written.
The practical methodology: establish a baseline with a known-good stable Proton version, then test Proton-GE using the YAAB benchmark scenario to measure actual frame time differences rather than relying on subjective feel.
Launch Options Baseline
The -noLauncher flag bypasses the default Arma 3 launcher's compatibility checks and module initialization, which adds startup time and can introduce thread contention. Before proceeding, note the naming convention: PROTON_NO_ESYNC=0 means ESync is active (the disable flag is off). PROTON_NO_ESYNC=1 means ESync is disabled. This double-negative pattern applies equally to PROTON_NO_FSYNC and to any other PROTON_NO_* variable you encounter. The Warning callout below repeats this because it is the single most commonly misconfigured pair in Linux gaming guides -- it is worth reading twice.
By default, Arma 3 on Proton will not connect to BattlEye-protected servers. The Bohemia Interactive community wiki is explicit: "By default, Arma 3 on Proton will not work on servers that use BattlEye. To enable BattlEye compatibility, download and install the 'Proton BattlEye Runtime' tool under the 'Tools' section in your steam library." [source: community.bistudio.com, Arma 3: Play on Linux] The vast majority of public Arma 3 multiplayer servers use BattlEye. Without this tool installed, you can play single-player and join unprotected servers, but you will be kicked from BattlEye servers before the mission loads. To install: open Steam, click the dropdown at the top of your library, select "Tools", locate "Proton BattlEye Runtime", and install it. No launch option changes are required -- Proton handles the rest automatically once the Runtime is installed.
The baseline string below sets both to =0 (both enabled) as a starting point only -- this is intentional so you can read the next section on ESync vs FSync before making the correct choice for your kernel. Running both simultaneously is redundant; ESync and FSync handle the same synchronization problem at different levels. After reading the next section, you will replace this baseline with one of the two correct configurations. Do not run the baseline string as your final configuration.
PROTON_NO_ESYNC=0 PROTON_NO_FSYNC=0 %command% -noLauncher -nosplash -skipIntro -world=empty
ESync and FSync: Synchronization Primitives
If you are running any maintained distribution with a kernel 5.16 or later -- which includes every distribution in active support as of 2026 -- use FSync only. Set PROTON_NO_ESYNC=1 PROTON_NO_FSYNC=0 and stop there. The background explanation below explains why. If you hit ESync-related mission-load hangs (see the Warning callout further down), drop to ESync only instead: PROTON_NO_ESYNC=0 PROTON_NO_FSYNC=1.
Windows synchronization primitives -- mutexes, semaphores, events -- are kernel objects that allow threads and processes to coordinate access to shared resources. They are fundamental to how Windows applications manage concurrent operations: every time one thread waits for another to finish, a synchronization primitive is involved. Wine originally emulated these by making system calls to the Linux kernel for every synchronization operation, which is enormously expensive. Each wineserver round-trip added overhead measured in microseconds, and Arma 3, which creates and synchronizes dozens of threads, was hitting this overhead thousands of times per second.
ESync replaced the wineserver round-trips with Linux eventfd file descriptors, allowing synchronization to happen entirely in userspace without kernel transitions in the common uncontested case. FSync goes further, using the futex_waitv() syscall which avoids file descriptor creation entirely and eliminates the high per-operation cost of eventfd at scale. As Collabora developer André Almeida described it: "Compared to a solution that uses eventfd(), futex was able to reduce CPU utilization for games, and even increase frames per second for some games. This happens because eventfd doesn't scale very well for a huge number of read, write and poll calls compared to futex." [source: GamingOnLinux, Linux Kernel 5.16 futex_waitv]
Older guides state that FSync requires a patched kernel (Zen, Liquorix, CachyOS, etc.). This was true before January 2022. The futex_waitv() syscall that FSync depends on was mainlined in Linux 5.16. Every distribution shipping kernel 5.16 or later -- which includes Ubuntu 22.04+, Fedora 36+, Arch rolling, Debian Bookworm, and anything in active maintenance as of 2026 -- already has FSync support without any out-of-tree patches. Custom kernels like Zen and CachyOS still carry the futex_waitv code, but they are no longer the only path to FSync. [source: Phoronix, Linux 5.16 futex_waitv]
Important historical disambiguation: there were two different implementations called "FSync" in community discussions. The original out-of-tree "fsync1" patch (from wine-tkg and similar custom builds) used a different internal mechanism and was never upstreamed. What landed in kernel 5.16 was the futex_waitv() syscall, which Wine and Proton then used for a second, distinct implementation also referred to as "FSync" or "FSync2" in some community posts. Proton builds from approximately 6.3-8 onward use only the mainlined futex_waitv-based implementation. If you encounter older forum threads discussing FSync requiring Zen/TkG kernels, they predate the 5.16 mainline merge and are no longer relevant for current distributions. [source: Collabora, futex_waitv and gaming on Linux, 2023]
You can check for FSync support at runtime with the following command. One important caveat: many security-hardened distributions set kernel.kptr_restrict to 1 or 2, which causes /proc/kallsyms to suppress symbol addresses or return empty results for unprivileged users. If the grep returns nothing, that may mean kptr_restrict is active rather than the syscall being absent. Check with cat /proc/sys/kernel/kptr_restrict -- a value of 0 means symbols are readable; 1 or 2 means they are restricted. The more reliable approach on any current distribution is simply to check your kernel version: if you are running 5.16 or later, futex_waitv is present. [source: kernel.org, kptr_restrict documentation]
# Method 1: grep kallsyms (may return empty if kptr_restrict is set) $ grep futex_waitv /proc/kallsyms # If empty, check kptr_restrict first: $ cat /proc/sys/kernel/kptr_restrict # Method 2 (more reliable): just check your kernel version -- 5.16+ has futex_waitv $ uname -r
A non-empty result from the grep, or a kernel version of 5.16 or later from uname -r, confirms futex_waitv is present. Remember: PROTON_NO_FSYNC=0 means FSync is enabled; PROTON_NO_FSYNC=1 disables it. If FSync is available, disable ESync and keep FSync active. FSync generally outperforms ESync because it uses kernel futexes rather than eventfd file descriptors -- it does not consume file descriptors for synchronization objects, eliminating the exhaustion problem ESync is subject to on heavily modded installs. The Collabora technical writeup on futex_waitv confirms this: compared to eventfd, futex was able to reduce CPU utilization and even increase frames per second for some games because "eventfd doesn't scale very well for a huge number of read, write and poll calls compared to futex." [source: Collabora, futex_waitv and gaming on Linux, 2023] If FSync is not available, use ESync only:
# FSync available (preferred) -- ESync disabled (=1), FSync enabled (=0) PROTON_NO_ESYNC=1 PROTON_NO_FSYNC=0 %command% # ESync only fallback -- ESync enabled (=0), FSync disabled (=1) PROTON_NO_ESYNC=0 PROTON_NO_FSYNC=1 %command%
PROTON_NO_ESYNC=0 means ESync IS enabled. PROTON_NO_FSYNC=0 means FSync IS enabled. The NO_ prefix is a disable flag, not a status. Setting both to =0 does not produce a neutral configuration -- it enables both simultaneously, which is wasteful and potentially harmful since both address the same synchronization problem through competing mechanisms. The intended configuration for any kernel 5.16 or later is PROTON_NO_ESYNC=1 PROTON_NO_FSYNC=0: ESync disabled, FSync active. This is the most commonly misconfigured pair of variables in Linux Arma 3 guides because the naming reads backwards from what you expect. If you take one thing away from this section before applying any launch options: =1 means the feature is turned OFF; =0 means it is turned ON.
ESync creates one eventfd file descriptor for every Windows synchronization object it emulates. Heavily modded Arma 3 installations, which can load hundreds of DLLs and synchronization objects, can exhaust the system file descriptor limit and produce eventfd: Too many open files errors that manifest as crashes or hangs. The ESync README documents the historical baseline: "esync creates one eventfd descriptor for each synchronization object, and some games may use a large number of these. Linux by default limits a process to 4096 file descriptors, which probably was reasonable back in the nineties but isn't really anymore." [source: README.esync, zfigura/wine] That 4096 figure is a historical default that many modern distributions have already raised significantly -- systemd-based distros (Ubuntu, Fedora, Arch, Debian Bookworm with systemd 240+) typically ship with hard limits of 524288 or higher. Check your actual current limit before assuming the worst:
# Check your current hard limit (should be 524288 or higher) $ ulimit -Hn # If it's 4096 or similar, add to /etc/security/limits.conf: * soft nofile 524288 * hard nofile 524288 # On systemd systems, also add to /etc/systemd/system.conf and /etc/systemd/user.conf: DefaultLimitNOFILE=524288
Most current distributions (Debian, Ubuntu, Fedora, Arch with systemd 240+) already ship with a high enough limit. This matters specifically when using ESync; FSync does not use file descriptors and is not affected.
Certain Arma 3 loading issues -- specifically freezing or crashing at mission load -- can be caused by ESync interaction with Wine thread handling. The Bohemia Interactive wiki documents this: "In some cases, the game may freeze, hang, crash or otherwise fail to progress past the loading screen after mission launch. The possible cause of the issue has to do with how wine handles threads... Disable Proton Esync by adding PROTON_NO_ESYNC=1." [source: community.bistudio.com] If you experience mission load hangs, disable ESync entirely even at the cost of some performance.
Layer 2: DXVK and the Graphics Translation Pipeline
What DXVK Does
DXVK translates Direct3D 8 through 11 API calls into Vulkan. Arma 3 uses DirectX 11, meaning every draw call, every shader compilation, every resource bind goes through DXVK before reaching your GPU driver. DXVK is bundled by Proton automatically, so you benefit from updates when you update Proton. [source: github.com/doitsujin/dxvk]
Shader Pre-Compilation and First-Session Stutter
The most significant source of stutter in Arma 3 on Linux is not the engine's single-thread bottleneck -- it is DXVK's shader compilation pipeline. When Arma 3 encounters a new shader combination for the first time, DXVK must compile a Vulkan shader from the captured DXBC (DirectX Bytecode). This compilation happens on-the-fly during rendering, causing frame time spikes that register as jarring stutters.
DXVK mitigates this through two distinct layers of caching, and most guides conflate them. The first is the GPU driver's Vulkan shader cache, managed by the driver itself (NVIDIA or RADV/Mesa) and stored by Steam at ~/.local/share/Steam/steamapps/shadercache/107410/. If you installed Arma 3 to a secondary Steam library, the path follows that library: <your_steam_library>/steamapps/shadercache/107410/. You can confirm your exact library paths in Steam under Settings > Storage. The second is DXVK's own internal shader cache, which stores compiled Vulkan shaders independently of the GPU driver cache. Since DXVK 2.0, the legacy .dxvk-cache state cache files were superseded by GPL; they were removed entirely in DXVK 2.7. [source: DXVK 2.7 release notes, GitHub -- "Legacy feature removal: The state cache was introduced in Version 0.80 to reduce shader compilation stutter, and has been largely unused since the introduction of VK_EXT_graphics_pipeline_library in DXVK 2.0. In order to reduce maintenance burden... this legacy feature was now removed."] Under Proton, DXVK's internal shader cache defaults to %LOCALAPPDATA%/dxvk inside the Wine prefix -- on the Linux filesystem, that translates to steamapps/compatdata/107410/pfx/drive_c/users/steamuser/AppData/Local/dxvk/. The GPU driver cache at the Steam shadercache path and DXVK's internal cache at the prefix path are separate and both warm independently. [source: DXVK README -- "DXVK_SHADER_CACHE_PATH: Path to internal shader cache files. By default, this will use %LOCALAPPDATA%/dxvk in a Windows or Wine environment."]
A source of confusion worth resolving explicitly: as of DXVK 2.7 (July 2025), no .dxvk-cache state cache files are written or read. The state cache was already functionally superseded in DXVK 2.0 when GPL took over its role; in 2.7 the remaining legacy code was formally removed. What DXVK's internal cache now contains is not shader state objects -- it is Vulkan pipeline objects managed through the GPL two-stage compilation pathway. These are distinct in nature from the old state cache files. If you find any .dxvk-cache files left over from a previous install, they are inert and can be safely deleted. If a guide tells you to manage or reset .dxvk-cache files on a current Proton version, that guide predates DXVK 2.7 and is no longer relevant. The GPU driver's own disk cache at the Steam shadercache path continues to function as before -- that layer is driver-managed and unaffected by the DXVK state cache removal. [source: DXVK v2.7 release notes, GitHub] Driver requirement note: DXVK 2.7 requires the Vulkan extension VK_KHR_maintenance5. The release notes confirm that drivers without it are no longer supported -- primarily affecting Windows users on AMD Polaris and Vega, but also any Linux user on very old Mesa or NVIDIA drivers. On Linux, AMD RDNA and CDNA GPUs running Mesa 25.0+ and NVIDIA GPUs on driver 550.54.14+ meet this requirement. If Proton has not updated your system's DXVK to 2.7 due to driver constraints, the state cache behavior described above reflects DXVK 2.0-2.6 instead, where GPL replaced the state cache functionally but the legacy code remained until 2.7. In practice, if you are running a current Proton version on any GPU released in the past four years, you are on DXVK 2.7+. [source: DXVK v2.7 release notes, driver support requirements]
On systems running Steam as a Flatpak -- the default install method on Fedora Silverblue, and increasingly common on other distributions -- the shadercache lives at a different location: ~/.var/app/com.valvesoftware.Steam/.local/share/Steam/steamapps/shadercache/107410/. If you are troubleshooting a cold-cache issue and cannot locate your shader cache at the standard path, check whether you installed Steam via Flatpak. Run flatpak list | grep Steam to confirm. The rest of the guide's paths assume a native Steam install; substitute the Flatpak prefix accordingly.
A fact almost entirely absent from Linux gaming guides: Arma 3 runs two independent caching processes on first launch, not one. The first is DXVK's shader compilation pipeline described above -- the GPU driver's Vulkan shader cache (stored by Steam at the shadercache path) and DXVK's own internal shader cache (stored inside the Wine prefix at AppData/Local/dxvk/) both warm as you encounter new shader combinations during gameplay. The second is Arma 3's own binary config cache -- the engine compiles its PBO (Packed Binary Object) addon data into binary form on first run, storing the result in the Wine prefix under the game's profile path (steamapps/compatdata/107410/pfx/drive_c/users/steamuser/My Documents/Arma 3/). The game must also write its initial Arma3.cfg and video configuration on that first launch. Before all caches are warm, performance is meaningfully worse than what you will see after several hours of play.
The practical consequence: any time you change Proton versions, delete the shader cache, or run a fresh install, all caches cold-start simultaneously. The stutter and frame time variance you experience in the first session is the compound effect of DXVK compiling Vulkan pipelines and Arma 3 building its binary addon cache. Do not evaluate your configuration changes, do not benchmark with YAAB, and do not conclude that your tuning is working or broken until you have played several hours across multiple maps and mission types. A single session is not a valid baseline regardless of how smooth it feels subjectively -- the first session with a cold cache will always feel worse than subsequent sessions even if the hardware and configuration are identical.
Proton version comparison caveat: when switching Proton versions specifically for benchmarking purposes, note that changing Proton versions does not automatically reset the Wine prefix or the DXVK internal shader cache. A valid apples-to-apples Proton version comparison requires either allowing both versions to fully warm their caches independently across multiple play sessions, or resetting the prefix and shader cache before each version's test run. Benchmarking version A with a warm cache against version B with a cold cache will always favor version A, even if version B is faster.
On first launch, stuttering will be heavy as all shader caches populate. On subsequent launches with warm caches, the experience improves substantially. Never benchmark or evaluate Arma 3 performance on Linux after a clean cache wipe -- always warm all caches first with several hours of play across varied maps and mission types before drawing any conclusions about configuration changes.
When diagnosing whether a specific crash or stutter pattern is caused by DXVK or by the engine itself, you can bypass DXVK and fall back to OpenGL via WineD3D by setting PROTON_USE_WINED3D=1 in your launch options. Performance will be significantly worse under WineD3D -- this is not a play configuration, only a diagnostic tool. If the crash or stutter disappears under WineD3D, the issue is DXVK-related. If it persists, the issue is in the engine or Wine layer beneath DXVK. Remove this variable immediately after diagnosis. Do not run Arma 3 long-term under WineD3D; it does not support the shader compilation pipeline optimizations that DXVK provides.
# Monitor DXVK in real time -- 'compiler' shows active shader compilation events, # which is the most useful flag specifically for diagnosing cache-warmup stutter. # Remove 'compiler' from daily use once your cache is warm; it adds visual clutter. DXVK_HUD=fps,memory,gpuload,compiler %command%
The -world=empty flag in your launch options instructs the Arma 3 engine not to load any terrain world on startup, which removes the main menu background world load that happens by default. This saves 5-15 seconds of load time and reduces cold-start memory pressure before you have chosen a mission. It does not affect mission loading, multiplayer connectivity, or mod loading -- those happen after you select a mission regardless. All consolidated launch strings in this guide include it. You can remove it if you prefer the animated background terrain in the main menu, but there is no performance benefit to keeping it.
DXVK_ASYNC=1 was a workaround patch maintained by community forks (notably Sporif's dxvk-async) that compiled pipeline shaders on background threads to reduce stutter. It was never part of official upstream DXVK -- rather, it was carried as an out-of-tree patch in GE-Proton and other community builds. When DXVK 2.0 shipped in November 2022 with native VK_EXT_graphics_pipeline_library support, GPL superseded the purpose of the async patch, and GE-Proton removed the dxvk-async patch in version GE-Proton7-45 (January 2023). The GE-Proton7-45 GitHub release notes state directly: "Upstream DXVK has implemented the GraphicsPipelineLibrary (GPL) back in August, which takes over dxvk-async's job." [source: GE-Proton7-45 release notes, GitHub] PCGamingWiki confirms: "Not supported anymore by the developer since it was superseded by graphics pipeline libraries support." [source: PCGamingWiki, DXVK] Setting DXVK_ASYNC=1 has no effect on any standard Proton or Proton-GE build. Do not include it in your launch options.
Precision note on dxvk-gplasync: A separate community fork called dxvk-gplasync is actively maintained and combines GPL with async. It ships in some niche builds (wine-tkg, certain AUR packages). It is not part of any standard Proton or Proton-GE release. Version note: as of dxvk-gplasync 2.7-1 (July 2025), the gplAsyncCache feature was removed to match upstream DXVK 2.7's removal of the legacy state cache. [source: dxvk-gplasync v2.7-1 release notes, GitLab] On gplasync 2.7-1+, async compilation is enabled via the DXVK_ASYNC=1 environment variable or dxvk.enableAsync = True in dxvk.conf. On pre-2.7-1 gplasync builds the variable also worked. If you encounter a guide recommending DXVK_ASYNC=1 and your Proton build responds to it, you are likely running a gplasync-patched build, not upstream DXVK. For standard GE-Proton or official Proton releases, the variable does nothing. [source: AUR dxvk-gplasync-bin package]
One nuance worth knowing: GPL does not behave identically to async. GPL compiles pipelines in two stages -- a fast "partial" pipeline runs immediately to avoid stutter, then an optimized full pipeline replaces it once compiled in the background. This means on a freshly warmed cache you may notice a brief second micro-stutter when the optimized version replaces the fast one. This is expected, not a regression, and diminishes as the optimized cache builds up over multiple play sessions. It is a different and generally superior pattern to the async-era stutter -- shorter, less frequent, and progressively improving rather than hitting every new shader encounter equally hard. [source: DXVK README, GPL shader compilation behavior]
If you consistently see GPU utilization well below 70 percent while frame rate is low, you are likely CPU-bottlenecked -- further GPU tweaks will not help. However, treat this as a starting signal, not a hard rule: GPU utilization can appear low because the workload is bursty and the GPU is frequently idling between heavy draw call batches, or because DXVK's compilation threads are consuming GPU time without it registering as "game" utilization. Cross-check with MangoHud's cpu_load metric: if one core is pegged at 100% while others sit idle, that is the main simulation thread at its ceiling.
Watch frame time, not average FPS: Arma 3's stutter pattern is not reliably captured by average FPS alone. Enable MangoHud's frame time graph with MANGOHUD_CONFIG=fps,frametime,cpu_load,cpu_stats,gpu_load in your launch options and watch the frametime line, not the FPS number. Consistent stutter shows as a spiky frametime graph even when average FPS looks acceptable. A smooth frametime graph with low FPS means you are simply GPU or CPU limited. A jagged frametime graph with high FPS means you are hitting scheduling or stall events -- shader compilation, OS latency, or audio thread contention. These have different remedies and are invisible in an average FPS readout.
Installing MangoHud: MangoHud is referenced throughout this guide for bottleneck diagnosis and benchmarking. Install it before you begin: Ubuntu/Debian: sudo apt install mangohud; Fedora: sudo dnf install mangohud; Arch: sudo pacman -S mangohud. The consolidated launch string in the final section includes MANGOHUD=0 so you can flip it to 1 for benchmarking sessions without restructuring the string. Flatpak Steam users: the native package manager installation of MangoHud will not inject into games running inside the Flatpak sandbox. Install the Flatpak runtime layer instead: flatpak install flathub org.freedesktop.Platform.VulkanLayer.MangoHud. Your MangoHud config file also lives at a different path inside the Flatpak environment: ~/.var/app/com.valvesoftware.Steam/config/MangoHud/MangoHud.conf. [source: GamingOnLinux, MangoHud guide]
AMD vs. NVIDIA on Linux
On AMD GPUs, the open-source RADV driver is generally superior for gaming compared to AMD's proprietary AMDVLK. Set AMD_VULKAN_ICD=RADV to force RADV if you find it falling through to AMDVLK. Community performance analysis notes that "Arma 3 favors NVIDIA GPUs because AMD's higher DX11 overhead becomes a bottleneck under the aforementioned engine limitations. This means an AMD GPU that should perform similarly to an equivalent NVIDIA GPU would perform noticeably worse in GPU-bound scenarios." [source: XenyxNZ Arma 3 Performance Guide, GitHub] For NVIDIA, the proprietary driver is essentially the only viable option. Ensure power management is set to maximum performance mode:
nvidia-settings -a "[gpu:0]/GpuPowerMizerMode=1"
Without this, NVIDIA's dynamic power management will clock down the GPU during brief gaps between heavy draw calls, introducing latency spikes that manifest as micro-stutters.
A Note on 3D V-Cache CPUs
Because Arma 3's main simulation loop is heavily cache-sensitive -- it repeatedly walks large entity tables, terrain data, and AI state that do not fit in L2 -- CPUs with AMD's 3D V-Cache technology deliver disproportionate performance gains compared to what clock speed or IPC alone would predict. The XenyxNZ performance guide notes directly: "Arma 3 is very responsive to CPU core performance, as well as cache size. Nowadays it's possible to get very good out-of-the-box frame rates using a CPU which utilizes AMD's 3D V-Cache technology -- these CPUs have significantly more on-board memory (cache), which is used to store frequently accessed data." [source: XenyxNZ Arma 3 Performance Guide, GitHub] Real-world YAAB benchmarks posted in the Arma 3 Steam community have shown players moving from a Ryzen 5 5600X to a Ryzen 7 5800X3D and going from average frames of around 49 to around 76 at 1440p -- a ~55 percent improvement from CPU alone, driven almost entirely by the L3 cache tripling from 32 MB to 96 MB. [source: Steam Community Arma 3 Discussions, YAAB 5600X vs 5800X3D comparison] Important caveat on this figure: the comparison is from a single community-reported swap on a specific system, not a controlled benchmark -- the two data points come from different play sessions that may have had different RAM configurations, mod loads, mission states, and Proton versions. The direction of the result (large gain from 3D V-Cache in Arma 3) is consistent with many independent reports and with the architectural reasoning about cache-sensitive workloads, but the exact percentage should be treated as illustrative rather than predictive for your hardware.
This matters for Linux users specifically: because Proton's Vulkan translation layer (DXVK) adds memory pressure on top of the game's own working set, a larger L3 cache reduces the rate at which the main thread stalls waiting for data from RAM. On Linux under Proton, the 3D V-Cache benefit is at least as pronounced as on Windows, and potentially more so because DXVK's pipeline state objects add to the hot data footprint that the cache must accommodate. If you are considering a CPU upgrade specifically for Arma 3 and are on AM4 or AM5, a 3D V-Cache part (5800X3D, 7800X3D, 9800X3D) is the highest-ROI single hardware change available.
Layer 3: Linux Kernel Parameters
The CPU Governor
Your CPU's frequency scaling governor determines how the kernel manages clock speeds. The powersave and schedutil governors throttle CPU frequency during lulls between heavy computation. In practice, because Arma 3's main thread must maintain consistent timing for the simulation loop, frequency drops during what the governor perceives as idle moments introduce frame time variance that looks like stuttering even when average frame rate is acceptable.
# Set immediately (all distributions) $ echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor # Set persistently (Debian/Ubuntu) -- cpupower is part of linux-tools, NOT a standalone package # "apt install cpupower" will fail with "package not found" -- use linux-tools instead: $ sudo apt install linux-tools-common linux-tools-$(uname -r) $ sudo cpupower frequency-set -g performance # Set persistently (Fedora) $ sudo dnf install kernel-tools $ sudo cpupower frequency-set -g performance # Set persistently (Arch) $ sudo pacman -S cpupower $ sudo cpupower frequency-set -g performance
vm.max_map_count
vm.max_map_count controls the maximum number of Virtual Memory Areas a process can create. Wine processes create VMAs heavily -- every loaded DLL, every anonymous mapping, every memory-mapped file creates one or more. The Linux kernel's default was designed for native applications. The kernel's html documentation confirms this value: "The default value is 65530." [source: kernel.org, sysctl/vm documentation] Note: the actual compiled-in default in the kernel source (DEFAULT_MAX_MAP_COUNT in include/linux/mm.h) is 65536, not 65530 -- the documentation has a long-standing minor discrepancy. Either value represents the unmodified kernel default. Wine-based games with large mod lists can exhaust this limit, resulting in allocation failures that manifest as crashes or hangs rather than clean error messages.
This is significant enough that multiple major Linux distributions moved to raise the default to 1,048,576. Fedora was the first major distribution to ship this change, landing it in Fedora 39 via a systemd sysctl drop-in. The Fedora change document is explicit: "It was agreed to set the limit to 1048576, up from the Linux default 65530. This was briefly discussed in #fedora-devel and received positively." [source: Fedora Project Wiki, Changes/IncreaseVmMaxMapCount] Arch Linux followed in April 2024 via its filesystem package: "The vm.max_map_count parameter will be increased from the default 65530 value to 1048576. This change should help address performance, crash or start-up issues for a number of memory intensive applications, particularly for (but not limited to) some Windows games played through Wine/Steam Proton." [source: Arch Linux News, April 2024] Ubuntu 24.04 LTS also raised the value to 1,048,576 via the procps package, matching Fedora's value -- GamingOnLinux confirmed the change was committed before the April 2024 release. [source: GamingOnLinux, March 2024] openSUSE Tumbleweed also raised the value. The practical result: if you are running Fedora 39+, Arch (post-April 2024), Ubuntu 24.04+, or openSUSE Tumbleweed, the value is already set. Ubuntu 22.04 LTS and Linux Mint 21.x (based on 22.04) still ship the old 65530 default and require the fix below. Check your actual current value before deciding whether to apply the fix:
$ cat /proc/sys/vm/max_map_count # If output is 65530 or 65536 (the unmodified kernel default -- both values # appear depending on distribution and kernel doc version), apply the fix below. # If output is already 1048576, your distro already ships this (Fedora 39+, # Arch post-April 2024, Ubuntu 24.04+, openSUSE Tumbleweed) -- skip.
$ echo "vm.max_map_count = 1048576" | sudo tee /etc/sysctl.d/99-gaming.conf $ sudo sysctl -p /etc/sysctl.d/99-gaming.conf
vm.swappiness
Arma 3 loads enormous terrain data, texture atlases, and object data into memory. The default vm.swappiness = 60 instructs the kernel to begin moving memory pages to swap even when RAM is not critically full. For a game workload where swap access latency is catastrophic, this is the wrong tradeoff. A value of 10 instructs the kernel to strongly prefer keeping application memory in RAM. For systems with 32 GB or more, you can push toward 1. Avoid 0, which can trigger the OOM killer under genuine memory pressure. One caveat: if your system uses zram or zswap (compressed in-memory swap, common on Fedora and modern Ubuntu configurations), the swap penalty is far lower than with a disk-based swap partition, because compressed swap lives in RAM. On zram-only systems, a slightly higher swappiness -- around 20-30 -- may be reasonable, since the kernel is swapping to memory rather than disk. Check with swapon --show to see what swap devices are active.
vm.swappiness = 10
Transparent Hugepages
Standard x86-64 pages are 4 KB. The CPU's TLB (Translation Lookaside Buffer) has limited capacity. AMD Zen 4 cores have 72 L1 dTLB entries and 3072 L2 TLB entries -- both counts increased from Zen 3's 64 and 2048 respectively, as documented in AMD's Zen 4 Software Optimization Guide and confirmed by microarchitecture analysis from Chips and Cheese: "The L1 DTLB's size [increased] from 64 entries to 72. The L2 TLB sees its entry count increased from 2048 to 3072." [source: Chips and Cheese, AMD's Zen 4 Part 2: Memory Subsystem, November 2022] Intel's Raptor Lake P-cores (Raptor Cove, a refresh of Golden Cove) carry 96 L1 dTLB entries and 2048 L2 STLB entries, as detailed in AnandTech's Golden Cove architecture coverage: "The L1 DTLB has grown from 64 entries to 96 entries -- Intel didn't mention the L2 TLB which would mean it's still at 2048 entries." [source: AnandTech, Golden Cove Microarchitecture Examined, August 2021] These are hierarchical, not additive: an L1 TLB miss triggers an L2 lookup, and an L2 miss triggers an expensive page table walk. When Arma 3's main thread walks through megabytes of entity data or terrain height maps, TLB misses force the CPU to walk the page table hierarchy at a cost of dozens to hundreds of cycles per miss. Hugepages solve this by using 2 MB pages instead of 4 KB pages, reducing the number of TLB entries needed by a factor of 512 for the same address range.
However, THP scanning and promotion can itself cause latency spikes. The madvise mode is the most defensible setting, allowing applications that explicitly request hugepage treatment to benefit without forcing promotion on all mappings:
$ echo madvise | sudo tee /sys/kernel/mm/transparent_hugepage/enabled $ echo defer+madvise | sudo tee /sys/kernel/mm/transparent_hugepage/defrag
Test with your specific workload. Use perf stat -e dTLB-load-misses,dTLB-store-misses to measure TLB miss rates before and after changes.
Layer 4: The Memory Allocator
Bohemia Interactive built Arma 3 with a replaceable memory allocator interface. The allocator runs on the critical path of the main simulation loop -- if malloc is slow, frames are slow. The Bohemia Interactive community wiki confirms: "Default allocator used by the engine is based on Intel TBB 4." [source: community.bistudio.com, Arma 3 Custom Memory Allocator]
An alternative the community has converged on is mimalloc, a compact general-purpose allocator developed by Microsoft. It is worth knowing mimalloc's actual origins precisely, because it is sometimes misdescribed online -- in particular, it is sometimes grouped with jemalloc, which was originally developed for Firefox. mimalloc's origins are unrelated. The official repository states plainly that mimalloc was "initially developed by Daan Leijen for the runtime systems of the Koka and Lean languages." [source: microsoft/mimalloc, GitHub] The 2019 technical report from Microsoft Research is equally clear: "While influenced by the allocation workload of the reference-counted Lean and Koka programming language, we show that mimalloc has superior performance to modern commercial memory allocators, including tcmalloc and jemalloc." [source: MSR-TR-2019-18, "Mimalloc: Free List Sharding in Action", Microsoft Research] It is a research allocator born in functional language runtimes, not browsers. Its design properties -- bounded allocation time, locality-optimized free lists, low fragmentation -- map well onto the tight allocation patterns of a simulation game's main loop running under Wine.
An Arma 3-compatible fork by GoldJohnKing is maintained on GitHub. [source: GoldJohnKing/mimalloc, GitHub] The installation process on Linux under Proton requires the Windows DLL build -- not a native Linux .so -- because the allocator substitution happens at the Windows PE executable level inside Wine, not at the Linux LD_PRELOAD level:
# Step 1: Download the DLL from GoldJohnKing's releases page. # Choose the _LockPages or _no-collect variant; check the release notes for recommendations. # The filename follows the pattern: mimalloc_vVER[_suffix].dll # Example: mimalloc_v219_20250213.dll or mimalloc_v217_LockPages.dll # Step 2: Copy the DLL to the game's Dll directory (inside the Proton Wine prefix) # Replace FILENAME.dll with the actual filename you downloaded: $ cp ~/Downloads/FILENAME.dll ~/.local/share/Steam/steamapps/common/Arma\ 3/Dll/ # Step 3: In your Steam launch options, the -malloc= value must match the DLL filename # without the .dll extension. For a file named mimalloc_v219_20250213.dll: -malloc=mimalloc_v219_20250213 # Step 4: Verify the allocator loaded by checking the RPT log after launch: $ grep -i malloc ~/.local/share/Steam/steamapps/compatdata/107410/pfx/drive_c/users/steamuser/AppData/Local/Arma\ 3/arma3_x64.rpt | head -5 # Expected output includes a line like: # Allocator: C:\...\Dll\mimalloc_v219_20250213.dll # If the allocator line shows tbb4malloc_bi instead, the DLL was not found -- check the path and filename match. # Note: arma3_x64.rpt is the standard performance binary log (both stable and Profiling branch). # arma3profiling_x64.rpt is written only by the separate instrumented diagnostics binary (not for daily use).
Arma 3's custom allocator system loads the DLL by matching the -malloc= parameter value to the DLL filename (without extension) in the game's Dll/ directory. If the strings do not match exactly -- including the version suffix -- the engine silently falls back to the default TBB allocator without producing an error visible during gameplay. This means you can think you are running mimalloc while actually running TBB. The only reliable way to confirm the allocator loaded is to check the RPT log immediately after launch as shown above. The Bohemia Interactive SITREP #00177 confirms the fallback behavior: "the game is still able to load any allocator it is commanded to via the start-up parameter" but the corollary is that a mismatched name produces silent fallback. [source: Bohemia Interactive SITREP #00177, dev.arma3.com] When GoldJohnKing releases a new version, update both the DLL in the Dll/ directory and the -malloc= value in your launch options simultaneously.
The XenyxNZ guide and Bohemia's own documentation recommend enabling "-hugepages" (large-page support) alongside mimalloc for the highest performance gains. On Windows this requires a "Lock Pages in Memory" Group Policy setting. On Linux the equivalent mechanism is Transparent Hugepages, configured earlier in this guide's kernel section. When THP is set to madvise and the defrag mode is set to defer+madvise, the kernel will promote mimalloc's large allocation regions to 2MB hugepages when mimalloc calls madvise(MADV_HUGEPAGE) on them -- which it does by default for large allocations. This means the THP configuration in the kernel section is the Linux equivalent of enabling large-page support for the allocator. You do not need an additional launch flag for this on Linux; the THP configuration handles it transparently. To verify hugepages are being used at runtime, check: grep -i hugepages /proc/meminfo -- a non-zero AnonHugePages value while the game is running confirms promotion is occurring. [source: kernel.org, Transparent Hugepage Support documentation]
The Profiling Branch
Bohemia Interactive maintains a separate Steam branch called the "Performance Profiling Build" that is the primary vehicle for Live Ops engine changes, experimental optimizations, and the multithreading improvements introduced in update 2.20. The branch no longer requires an access code. The official Profiling Branch page states directly: "It can now be used without access code." [source: dev.arma3.com/profiling-branch] The access code CautionSpecialProfilingAndTestingBranchArma3 documented in older guides including SITREP #00088 is no longer required -- confirmed additionally in SITREP #00241: "Profiling Branch... no longer does it require an access code." [source: Bohemia Interactive SITREP #00241, dev.arma3.com] Access it through Steam > Arma 3 Properties > Betas > select "profiling - Performance Profiling Build" from the dropdown.
This is the single most commonly misunderstood aspect of the Profiling branch. When you opt in and launch Arma 3 normally through Steam, you are running the performance-optimized binary (arma3_x64.exe), which is what you want. The branch also ships a second set of executables with "profiling" in their filenames (e.g. arma3profiling_x64.exe). These contain additional diagnostic instrumentation for developers. Bohemia's official page is explicit: "These binaries contain an extra suite of diagnostics, which makes them meant only for specific use cases (such as on request by one of our engineers). For most people we do not recommend using these, and therefore sticking to the branch defaults." [source: dev.arma3.com/profiling-branch]
The practical consequence for Linux users: when verifying which binary ran via the RPT log filename, the meaning is the opposite of what many guides state. If your RPT log is named arma3_x64.rpt, you are running the performance build -- the correct one. If it is named arma3profiling_x64.rpt, you are running the instrumented diagnostics build, which carries extra overhead and is not intended for daily use. The standard Steam launch path always runs the performance binary; the profiling binary requires explicitly renaming executables or launching via a custom batch file.
As of 2025-2026, the Profiling branch may receive builds up to several times per week and is the delivery mechanism for 2.20's multithreading overhaul, PhysX improvements, and updated mimalloc allocator builds. The OPREP for 2.20 documents the headline outcome: frame times "should feel more consistent, with fewer spikes" while average FPS "has only received a minor bump." [source: Bohemia Interactive OPREP Performance Optimizations in 2.20, dev.arma3.com] Community YAAB benchmark submissions in the Steam Workshop comments thread have recorded gains of 40-55% in AI-intensive scenarios on the Profiling branch performance binary with mimalloc v217 -- driven by the compound effect of the multithreading overhaul, the improved allocator, and reduced stall time on secondary worker threads. [source: YAAB Steam Workshop comments, community benchmark submissions] Critical context: these are community-aggregated figures from AI-heavy scenarios on specific hardware configurations, not controlled benchmarks. Gains are highly mission-specific -- a low-AI scenario on modern hardware may show 5-10% improvement; a heavily-modded mission with dense AI on an AM4 system may approach the upper end of that range. Do not treat these figures as predictive for your hardware. The compound gain also requires the Profiling branch performance binary, mimalloc, large-page support via THP, and a fully warmed cache simultaneously -- any missing component reduces the figure proportionally.
The Profiling branch is documented as multiplayer-compatible with the main branch: "As a rule, this branch is multiplayer-compatible with the default main branch. Both players and servers can interchange these branches and should be able to play." [source: dev.arma3.com/profiling-branch] However, the branch receives no in-depth pre-release testing, meaning new instabilities can appear. Profiling Branch updates do not reset your mod subscriptions, but if the branch introduces a binary incompatibility with a specific mod's DLLs, you may need to revert to the stable branch temporarily. Verify your branch build number matches community expectations before joining competitive servers.
Steam's beta branch system sometimes fails to apply the branch switch silently -- most commonly if you have Arma 3 installed on a secondary library drive or if a Steam update interrupted the download. Do not assume you are on the Profiling branch just because you selected it in the UI. There are two reliable ways to confirm at runtime:
Method 1: Check the build number in-game. From the Arma 3 main menu, use the version string visible at the bottom-left of the main menu screen. The build number for Profiling branch builds is higher than the current stable build. Cross-reference against the Bohemia SITREP or Profiling Branch page (dev.arma3.com/profiling-branch) which lists the current version at the top of the page.
Method 2: Check the log file name. When using the standard Steam launch path on the Profiling branch, the log is written to arma3_x64.rpt -- this confirms the performance binary ran. The log directory is inside the Wine prefix at steamapps/compatdata/107410/pfx/drive_c/users/steamuser/AppData/Local/Arma 3/. If you find arma3profiling_x64.rpt instead, the instrumented diagnostics binary ran -- not what you want for performance use. If no arma3_x64.rpt file with a recent timestamp exists at all, the branch switch did not take effect: restart Steam and verify the beta branch selection persisted under Arma 3 Properties > Betas.
Layer 5: I/O Subsystem Tuning
Arma 3 uses a streaming architecture for world assets. As you move across terrain, the engine continuously loads terrain chunks, texture mipmaps, vegetation models, and building assets from disk. For NVMe SSDs, the Kyber scheduler is specifically designed for low-latency solid-state storage, maintaining separate read and write queues with latency targets rather than throughput optimization. Gaming workloads are read-dominated and latency-sensitive, making Kyber a strong choice.
ACTION=="add|change", KERNEL=="nvme[0-9]*n[0-9]*", ATTR{queue/scheduler}="kyber"
ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/scheduler}="mq-deadline"
The udev scheduler rules above match single NVMe devices and single SATA drives. If your game partition lives on a software RAID array (Linux mdraid) or an LVM volume spanning multiple physical devices, the kernel device names will differ -- md0, md1, or logical volume paths rather than nvme0n1 or sda. The Kyber scheduler is designed for single low-latency SSDs and does not necessarily yield the same benefit on a multi-device array whose queuing behavior is managed by the RAID layer. For mdraid arrays on NVMe, set the scheduler on the individual constituent NVMe devices rather than the MD device itself -- the MD device's queue scheduler is typically none by design, as the RAID layer handles its own I/O scheduling. Verify your device topology with lsblk before writing udev rules for array configurations.
Also mount your game partition with noatime to eliminate unnecessary write traffic from access timestamp updates. Arma 3's streaming asset reads involve thousands of small file accesses per minute, and atime updates on each read add measurable I/O latency. A note on mount options: relatime is a safer alternative that satisfies POSIX requirements (it only writes atime when the file has been modified since last access) while still eliminating per-read write overhead. For a dedicated gaming partition, the difference between noatime and relatime is negligible, but if you share the partition with software that checks access timestamps (some build tools and mail clients do), relatime avoids edge-case breakage.
If your game partition uses btrfs -- the default filesystem on Fedora Workstation and openSUSE -- you already benefit from this optimization. btrfs uses relatime semantics by default and does not write atime on every read access. You do not need to add noatime to your fstab for a btrfs partition; doing so is harmless but redundant. If you are on ext4, XFS, or another filesystem, the noatime/relatime fstab entry below applies.
UUID=xxxx /games ext4 defaults,noatime 0 2
Layer 6: Audio -- The Silent Stutterer
A frequently overlooked source of Arma 3 stutter on Linux is the audio subsystem. Wine routes audio through PulseAudio or PipeWire, and buffer configuration matters significantly. Before applying any fix, identify which audio server you are actually running:
$ pactl info | grep "Server Name" # PulseAudio server → apply PulseAudio fix below # PipeWire server → apply PipeWire fix below
Under PulseAudio, the default fragment size configuration can cause audio thread wake-ups that compete with the rendering thread on the same CPU core:
default-fragments = 5 default-fragment-size-msec = 2
The settings above -- 5 fragments at 2 ms each -- give a total buffer of 10 ms with wakeup events every 2 ms. The PulseAudio default is typically default-fragment-size-msec = 25, meaning far less frequent wakeups. Reducing the fragment size to 2 ms increases how often the audio thread wakes up, which on a CPU-bottlenecked system can add measurable overhead rather than reduce stutter. If you apply this change and observe higher overall CPU load or new stutters in CPU-intensive engagements, increase default-fragment-size-msec incrementally (try 5, then 10) until the behavior stabilizes. The goal is the smallest value that eliminates audio crackling without overloading the CPU with wakeup overhead -- 2 ms is a starting point, not a universal optimum.
Under PipeWire -- the modern default on Fedora, Ubuntu 22.10+, and most current distributions (note: Ubuntu 22.04 LTS shipped PulseAudio by default; verify your specific version before assuming PipeWire is active) -- if you experience audio crackling during intense combat, adjust the quantum size. 512 samples at 48 kHz gives a buffer period of approximately 10.67 ms (512 ÷ 48000 = 0.01067 s), a reasonable balance between latency and CPU overhead. At 44.1 kHz -- which some output devices use instead -- the same 512-sample quantum gives approximately 11.6 ms (512 ÷ 44100 = 0.01161 s). The correct period depends on your output device's actual sample rate, not a fixed assumption of 48 kHz. Check your device's rate with pw-top or pactl info. Note that this is the buffer fill period, not the full round-trip audio latency -- actual latency will be higher by one or more periods depending on driver and application buffering.
There is an important distinction between two different audio buffer settings that guides frequently conflate. The PipeWire graph quantum (set via pw-metadata) is the PipeWire processing buffer size -- the number of samples the PipeWire graph processes per cycle. The ALSA hardware period size (set via WirePlumber's monitor.alsa.rules) is a separate, lower-level ALSA driver buffer that controls how often the hardware generates IRQs. These operate at different layers. The session-only pw-metadata command below sets the graph quantum. The persistent WirePlumber file sets the ALSA period size and headroom for your output device. Both can help, but they are not equivalent -- applying one does not substitute for the other. Start with the session-only quantum command to test; if that resolves your audio stutter, add the persistent quantum config. If you also experience crackling under heavy load, add the ALSA period size rule.
The pw-metadata command below sets the quantum for the current session only and is lost on reboot or PipeWire restart. To make it persistent, set it via a pipewire.conf.d fragment rather than via WirePlumber's ALSA monitor rules, since the graph quantum is owned by the PipeWire daemon, not WirePlumber:
$ pw-metadata -n settings 0 clock.force-quantum 512
context.properties = {
default.clock.quantum = 512
default.clock.min-quantum = 512
default.clock.max-quantum = 512
}
Restart PipeWire after adding the file: systemctl --user restart pipewire pipewire-pulse wireplumber. Separately, if your output device shows crackling that survives the quantum fix, the following WirePlumber rule sets the ALSA hardware period size for your output device. Note: this file format requires WirePlumber 0.5+, which ships with Fedora 40+, Arch (late 2024+), and Ubuntu 24.10+. Ubuntu 24.04 LTS ships WirePlumber 0.4.17, and Linux Mint 22 (based on Ubuntu 24.04 Noble) also ships WirePlumber 0.4.17 -- both definitively use the older Lua config approach, not the SPA-JSON format shown below. [source: UbuntuHandbook, WirePlumber on Ubuntu 24.04] If you are on Ubuntu 24.04 or Mint 22, use the older Lua config method or upgrade WirePlumber from the pipewire-debian PPA before attempting this file. The WirePlumber documentation confirms the 0.5 format change: "Starting with WirePlumber 0.5, this is the only file that WirePlumber reads to load configuration... Lua configuration files are no longer supported." [source: WirePlumber 0.5 documentation, pipewire.pages.freedesktop.org]
monitor.alsa.rules = [
{
matches = [ { node.name = "~alsa_output.*" } ]
actions = {
update-props = {
api.alsa.period-size = 512
api.alsa.headroom = 0
}
}
}
]
A Wine-specific fix for Arma 3 audio crackling is to override the XAudio2 DLL, forcing the game to use Wine's native XAudio2 implementation rather than a Wine-wrapped Windows one. The Bohemia Interactive community wiki documents two equivalent methods: [source: community.bistudio.com, Arma 3: Play on Linux]
WINEDLLOVERRIDES="xaudio2_7=n" %command% -noLauncher
# Install protontricks first (pip, AUR, or distro package), then: $ protontricks 107410 winecfg # In winecfg: Libraries tab → add xaudio2_7 → set to "native (builtin)"
The WINEDLLOVERRIDES environment variable sets DLL load order at the process level and is simpler to apply. The protontricks winecfg method writes the override directly into the Wine prefix registry and is the more persistent approach -- it survives Proton version changes that might reset environment-level overrides. If the first method does not resolve your audio crackling, use the second.
Arma 3's XAudio2 path depends on the Wine prefix's Windows version setting. When the prefix is configured as Windows 10 (which some Proton versions set by default), the game may load xaudio2_9 instead of xaudio2_7. In that case, xaudio2_7=n has no effect because the game is not loading that DLL. If the xaudio2_7 override does not resolve your audio issues, also try WINEDLLOVERRIDES="xaudio2_9=n" -- or try adding both: WINEDLLOVERRIDES="xaudio2_7=n,xaudio2_9=n". You can confirm which DLL the game is actually loading by checking the Arma 3 startup log (arma3.RPT) for XAudio2 initialization messages. [source: community.bistudio.com, Arma 3: Play on Linux]
Layer 7: Gamemode and CPU Affinity
gamemode is a daemon developed by Feral Interactive that applies performance-oriented system changes when a game process starts and reverts them on exit. It handles CPU governor switching, I/O priority elevation, and GPU performance profile integration automatically, without requiring persistent system changes. [source: github.com/FeralInteractive/gamemode]
gamemode can optionally renice the game process to a higher scheduling priority, which helps Arma 3's simulation thread compete more favorably with background Wine processes, PipeWire/PulseAudio threads, and system daemons for CPU time. This is controlled by the renice key under [general] in ~/.config/gamemode.ini. One critical precision point almost universally misreported in Linux gaming guides: the default value of renice in gamemode is 0, meaning no renicing is applied out of the box. The gamemode example.ini states this explicitly: "GameMode can renice game processes. You can put any value between 0 and 20 here, the value will be negated and applied as a nice value (0 means no change). Defaults to 0." [source: FeralInteractive/gamemode example/gamemode.ini, GitHub] The "negated" part of that sentence is the key mechanic: a config value of renice=10 causes gamemode to apply nice -10 (high priority), not nice 10 (low priority). The PAM limits shipped by gamemode cap the maximum renice at -10, which is why the value range in the config is 0–20 but the Arch Wiki notes: "By default, GameMode provides PAM limits that allow changing the scheduling priority up to a maximum of -10." [source: Arch Wiki, GameMode] To enable renicing for Arma 3, add the following to ~/.config/gamemode.ini and ensure your user is in the gamemode group (sudo usermod -aG gamemode $(whoami), then reboot):
# renice=10 means nice -10 (high priority). Default is 0 (no change). # PAM limits cap this at -10, so values above 10 will fail unless you # raise the limit in /etc/security/limits.d/10-gamemode.conf [general] renice=10
The gamemode README documents a subtle failure mode when aggressive nice values are combined with GPU-bound workloads: "some games may actually run seemingly slower with SCHED_ISO if the game makes use of busy looping while interacting with the graphic driver. The same may happen if you apply too strong nice values. This effect is called priority inversion: Due to the high priority given to busy loops, there may be too few resources left for the graphics driver. Thus, sane defaults were chosen to not expose this effect on most systems." [source: FeralInteractive/gamemode daemon/README.md, GitHub] For Arma 3 specifically, a renice of 10 (nice -10) is at the top of the default PAM limit. If you apply this and see worse 1% lows or new stutters during GPU-intensive scenes (heavy particle effects, large view distances), reduce the value to 5 or 4 and benchmark again. The right value is workload-specific.
If you find that gamemoderun is causing unexpected scheduling behavior or you want to confirm what it is actually doing at runtime, run gamemoded -d in a terminal to see its actions in real time. [source: FeralInteractive/gamemode README]
One important caveat: gamemode's CPU governor switching only works when the user process has permission to write to /sys/devices/system/cpu/*/cpufreq/scaling_governor. On distributions that restrict this path via polkit or udev rules, gamemode may silently fail to switch governors without reporting an error. Verify the governor actually changed before and after launching with gamemoderun:
# Check current governor before launching $ cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor # Launch with gamemoderun, then check again while game runs # Expected: schedutil or powersave → performance # If file check is inconvenient mid-session, use MangoHud instead: # Add cpu_mhz to MANGOHUD_CONFIG -- if frequency stays flat under load, # the governor is not switching (polkit/udev may be blocking it) MANGOHUD_CONFIG=fps,cpu_load,cpu_mhz gamemoderun %command%
Note: -cpuCount is intentionally absent here. Do not add it unless you are also using taskset to restrict affinity (see the callout below). The engine auto-detects cores correctly without it; adding it unconditionally is counterproductive on 2.20+.
Because Arma 3 is so dependent on a single thread, pinning that thread to a specific physical core prevents the kernel scheduler from migrating it between cores, which forces L1/L2 cache warm-up on each migration. The most effective approach on Linux is to set CPU affinity at the OS level after the process starts. Critical caveat for 2.20+ users: the multithreading overhaul in update 2.20 means Arma 3 now dispatches meaningful work to secondary threads -- explosion damage, physics, and other parallelized tasks. Aggressively restricting the process to only a few cores may prevent the secondary threads from running freely, introducing the very stalls the affinity pinning was meant to eliminate. The 2.20 OPREP warns directly: "If you are using an external tool to adjust the 'Process Affinity', which reduces the number of CPU cores the game can access, you need to use -cpuCount to inform the game about how many cores it can actually use." [source: Bohemia Interactive OPREP Performance Optimizations in 2.20, dev.arma3.com]
The OPREP is explicit: "Only use the -cpuCount parameter if you want to intentionally limit how many CPU cores the game can use. The engine can already automatically detect how many cores you have, so you do not need to tell it that." [source: dev.arma3.com, OPREP 2.20] On 2.20+, using -cpuCount without a specific reason (such as pairing it with taskset affinity restriction) is likely to hurt performance -- old guides that told you to set -cpuCount=N as a general boost are now counterproductive. The engine auto-detects correctly. Additionally, Bohemia confirms that "The -enableHT parameter has no effect if -cpuCount is set, so setting both is wrong." If you are using taskset, add -cpuCount=N where N is the number of physical cores (not logical CPUs/hyperthreads) in the taskset mask. The core IDs in the example below assume Intel SMT topology where even-numbered logical CPUs map to physical cores. AMD Ryzen and Intel hybrid (P-core/E-core) architectures use different numbering schemes. Always verify your actual CPU topology first with lscpu -e or lstopo before applying taskset. [source: man7.org, lscpu man page] On the Profiling branch with 2.20+, benchmark before and after -- affinity pinning may be net negative on high core-count CPUs.
What about -exThreads? You will encounter -exThreads=N in many older Arma 3 Linux guides. This parameter controls legacy thread usage for file I/O and rendering. As of 2.20, the engine's multithreading overhaul and auto-detection make manual exThreads tuning counterproductive for the same reason as -cpuCount. The Bohemia OPREP confirms the engine now manages its own thread distribution. Do not add -exThreads to your launch options unless you are specifically troubleshooting a documented edge case. Any guide that tells you to set -exThreads=7 as a general optimization was written before the 2.20 overhaul and is now outdated.
# Step 1: verify your actual CPU topology FIRST $ lscpu -e # Step 2: inspect ALL Arma processes -- do NOT pipe to head yet # Under Proton, Wine spawns multiple processes with similar names. # Identify the game process by highest CPU% before selecting its PID. $ ps aux | grep arma3_x64.exe # Step 3: once you have confirmed the correct PID from ps output, # assign affinity to that specific PID (replace NNNN with actual PID): $ taskset -cp 0,2,4,6 NNNN # Step 4: on 2.20+ Profiling branch, add to launch options ONLY if using taskset: # -cpuCount=4 (matching the 4 physical cores in the taskset mask above) # Note: -cpuCount counts native/physical cores, not logical CPUs/hyperthreads. # Per the 2.20 OPREP, do NOT use -cpuCount for general tuning -- only pair it # with taskset affinity restriction. Engine auto-detects cores correctly without it.
Gamescope: Bypassing the Desktop Compositor
Gamescope is a micro-compositor developed by Valve, originally for Steam Deck, that sandboxes a game into its own isolated Xwayland environment and composites its frames directly to screen using async Vulkan compute or DRM/KMS plane flips. The Gamescope GitHub README describes the core mechanism: "It's getting game frames through Wayland by way of Xwayland, so there's no copy within X itself before it gets the frame. It can use DRM/KMS to directly flip game frames to the screen, even when stretching or when notifications are up, removing another copy. When it does need to composite with the GPU, it does so with async Vulkan compute." [source: ValveSoftware/gamescope README, GitHub]
For Arma 3 on Linux, Gamescope addresses two specific practical problems that gamemode alone cannot solve:
1. Desktop compositor overhead on Wayland. When running under a standard Wayland compositor like GNOME Mutter or KDE KWin, every game frame passes through the compositor's render pipeline before reaching the display. Gamescope bypasses this entirely by taking ownership of the display through KMS/DRM plane flips when in embedded mode, or by handling composition itself in nested mode. The result is lower and more consistent frame delivery latency. For Arma 3's simulation thread -- which is highly sensitive to main-thread stall time -- reducing compositing-introduced jitter is meaningful.
2. Multi-monitor mouse capture. Without Gamescope, Arma 3 running in fullscreen on a multi-monitor setup under Proton can leak the mouse cursor to a secondary monitor when the in-game camera approaches screen edges, causing the game to lose focus and minimize. The Bohemia community wiki documents the workaround via winecfg's "automatically capture the mouse" setting, but that workaround is fragile. Gamescope solves this at the compositor level by sandboxing the game into its own virtual display where cursor escape is impossible -- the Arch Wiki notes Gamescope's use of --force-grab-cursor for precisely this scenario. [source: Arch Wiki, Gamescope]
# Install via package manager first: # Arch: sudo pacman -S gamescope # Ubuntu: sudo apt install gamescope # Fedora: sudo dnf install gamescope # Grant Gamescope elevated scheduling priority (prevents frame delivery jitter): $ sudo setcap 'CAP_SYS_NICE=eip' $(which gamescope) # Minimal launch option (nested mode -- runs inside your desktop): # Replace 1920 1080 with your actual native resolution. # -r sets frame rate limit; match your monitor's refresh rate or set to 0 for unlimited. gamescope -w 1920 -h 1080 -W 1920 -H 1080 -r 165 -- %command% -noLauncher # Add --force-grab-cursor for multi-monitor mouse capture issues: gamescope -w 1920 -h 1080 -W 1920 -H 1080 -r 165 --force-grab-cursor -- %command% -noLauncher # Add --adaptive-sync if your monitor supports VRR/FreeSync/G-Sync Compatible: gamescope -w 1920 -h 1080 -W 1920 -H 1080 -r 165 --adaptive-sync -- %command% -noLauncher # FSR upscaling (render at lower res, upscale to native -- see WINE_FULLSCREEN_FSR below # for the alternative Wine-level approach. Gamescope's -F fsr operates post-composition): gamescope -w 1280 -h 720 -W 1920 -H 1080 -F fsr --sharpness 4 -- %command% -noLauncher
A known Gamescope issue affects some users: when launched from Steam (as opposed to the command line), Gamescope can develop a periodic stutter after approximately 24 to 30 minutes of runtime. This is documented on the Arch Wiki as the "Gamescope Lag Bomb" and is related to how Steam's overlay interacts with the Gamescope WSI layer. [source: Arch Wiki, Gamescope] If you encounter it, try setting ENABLE_GAMESCOPE_WSI=0 before the Gamescope command in your launch options, which disables the Gamescope WSI layer and has resolved the issue for some users at the cost of some performance optimizations. Additionally, MangoHud's standard method does not function inside Gamescope -- if you need overlay metrics alongside Gamescope, use Gamescope's own --mangoapp flag rather than the MANGOHUD=1 environment variable. Gamescope and gamemode can be combined: place gamemoderun after the -- separator alongside the game command.
WINE_FULLSCREEN_FSR: Resolution Scaling Without a Native Implementation
Arma 3 has no native FSR or DLSS implementation. On hardware where the GPU is the frame rate bottleneck -- typically at 1440p or 4K with high view distances -- the Wine-level FSR upscaling available in GE-Proton can recover meaningful frames at the cost of some image sharpness. This feature works through Proton's existing fullscreen hack (fshack) scaler, replacing its standard linear upscaling with AMD FSR 1.0. It was introduced in Proton GE-Proton6-13 and is available in all subsequent GE-Proton releases. It is not available in official Valve Proton releases without manual patching. The mechanism is described precisely in Valve's Wine repository pull request: the FSR shader is injected into Proton's fshack scaler that intercepts the game's resolution change request, so it operates on the fully composited output frame -- including HUD and UI -- which is different from how native FSR implementations work. [source: ValveSoftware/wine pull #116, GitHub]
# Requires: GE-Proton installed via ProtonUp-Qt, selected for this game in Properties # Step 1: In Arma 3's video settings, set resolution BELOW your monitor's native res. # Step 2: Enable FSR -- it upscales from the in-game res to your native monitor res. # The game must be running in Fullscreen mode (not Borderless Window) for FSR to activate. # Basic enable (FSR 1.0 quality is determined by your chosen in-game resolution): WINE_FULLSCREEN_FSR=1 %command% # With sharpening control (0 = maximum sharpening, 5 = minimum; default is 2 in current GE-Proton): # Note: the original 6.13-GE-1 release defaulted to 5; the default was later changed to 2. # AMD recommends 2 as the balanced starting point. WINE_FULLSCREEN_FSR=1 WINE_FULLSCREEN_FSR_STRENGTH=1 %command% # GE-Proton7-25+ supports named quality presets via WINE_FULLSCREEN_FSR_MODE. # This automatically sets the render resolution to the correct FSR scaling ratio. # Modes: ultra (1.3x scaling), quality (1.5x), balanced (1.7x), performance (2.0x) # Example: enable FSR Quality mode (1.5x scaling, game renders at 1280x720 on a 1080p display): WINE_FULLSCREEN_FSR=1 WINE_FULLSCREEN_FSR_MODE=quality %command% # Manual preset equivalents if not using WINE_FULLSCREEN_FSR_MODE # AMD FSR 1.0 quality presets at 1920x1080 output: # Ultra Quality: set in-game res to 1477x831 # Quality: set in-game res to 1280x720 # Balanced: set in-game res to 1129x635 # Performance: set in-game res to 960x540 # For 2560x1440 output, multiply each dimension by 1.333. # For 3840x2160 output, multiply each dimension by 2.0.
Both methods upscale a lower resolution to native, but they differ in where they apply FSR in the rendering pipeline. WINE_FULLSCREEN_FSR operates through Wine's fullscreen hack layer at the Proton-Wine level, before frames reach the compositor. Gamescope's -F fsr operates at the compositor level, after frames are ready for display. For Arma 3 specifically: if you are already using Gamescope, use Gamescope's -F fsr and lower the -w/-h render resolution rather than the Arma in-game resolution setting -- this avoids the need to match the resolution to an FSR preset table manually. If you are not using Gamescope, WINE_FULLSCREEN_FSR=1 is the simpler path. Do not combine both methods simultaneously -- applying FSR twice produces a doubly-upscaled image with compounded artifacts. Also note that both methods apply FSR to the entire frame including the HUD and on-screen text, which can appear softer than at native resolution; this is an inherent limitation of both approaches compared to a native engine-level FSR integration.
Benchmarking Your Changes: The YAAB Methodology
Every change described in this guide should be tested rather than assumed. YAAB (Yet Another Arma Benchmark) by Sams is the standard community benchmarking scenario. The community guide describes it: "YAAB is a widely used Arma 3 performance benchmarking scenario made by Sams. It's ideal for measuring differences in performance while experimenting with parameters, overclocking, graphical settings, memory allocators and profiling builds." [source: XenyxNZ Arma 3 Performance Guide, GitHub] Subscribe to it on the Steam Workshop and run it consistently -- same settings, same mods, same time of day in the scenario -- before and after each change.
Record average FPS, minimum FPS (1% low), and 0.1% low values. For Arma 3, the 1% and 0.1% lows matter more than average. A change that raises average FPS from 50 to 55 but reduces 1% lows from 30 to 20 is a net negative for perceived smoothness. The community guide adds: "To determine a proper average, it is recommended to run the benchmark 3-5 times. Results can vary slightly without changing anything. This is due to the AI heavy nature of the benchmark, and Arma 3 being sensitive to fluctuating CPU and RAM resources." [source: XenyxNZ Arma 3 Performance Guide, GitHub]
MangoHud's frame_timing overlay is particularly diagnostic here. A flat line with consistent 20 ms frames is a better experience than one averaging 12 ms but spiking to 80 ms, even if the numeric average FPS is higher. When interpreting a frame time graph, look for:
- Regular sawtooth pattern on first session: typically DXVK shader compilation. Disappears after all caches warm. Expected and normal. Remember there are multiple parallel warmup curves -- the GPU driver's Vulkan shader cache, DXVK's internal shader cache, and Arma's binary addon cache -- and all must warm before you have a valid baseline.
- Brief second micro-stutter after cache warm-up (modern DXVK 2.x): this is the graphics pipeline library replacing a fast partial pipeline with an optimized full pipeline in the background. Shorter and less frequent than first-session stutter, and diminishes progressively over multiple sessions. Not a regression -- it is the expected behavior of GPL. See the DXVK section above.
- Isolated spikes, irregular timing: often kernel scheduler migration, I/O contention during asset streaming, or audio thread interference. These are the target of the kernel and I/O tuning in this guide. On the 2.20 Profiling branch, also check whether
tasksetaffinity restrictions are starving the secondary worker threads that now handle explosion and physics parallelism. - Sustained plateau at a high frame time: CPU main thread is saturated. This is the engine bottleneck. No amount of kernel tuning resolves it -- reduce simulation complexity (view distance, unit count) or upgrade hardware. On 2.20+, try the Profiling branch performance binary (not the instrumented profiling binary -- see the Profiling Branch section above) with mimalloc v217 and large-page support before concluding the ceiling is hardware-imposed; the 2.20 overhaul has resolved plateau scenarios in AI-heavy missions that previously appeared hard-bottlenecked.
MANGOHUD=1 MANGOHUD_CONFIG=fps,frame_timing,cpu_load,gpu_load gamemoderun %command%
Frame time graphs expose the shape of performance degradation in ways that average FPS never can. A flat graph with consistent 20ms frames is a better experience than one with 12ms average but 80ms spikes, even if the numeric average FPS is higher.
Consolidated Launch Options Reference
The following is a reasoned, buildable launch options string for Arma 3 on Linux under Proton. The string uses PROTON_NO_ESYNC=1 PROTON_NO_FSYNC=0 -- ESync disabled, FSync enabled -- which is the correct configuration for any kernel 5.16 or later (see the ESync/FSync section for how to verify). Do not run both set to =0 simultaneously; they address the same synchronization problem and running both wastes resources without benefit. If your kernel predates 5.16, swap to PROTON_NO_ESYNC=0 PROTON_NO_FSYNC=1 instead. MANGOHUD=0 is included explicitly so you can flip it to 1 for benchmarking sessions without restructuring the string -- setting it explicitly rather than omitting it also prevents any system-level MangoHud environment variable from accidentally enabling the overlay in production play. The -world=empty flag instructs the engine not to load any terrain world on startup, removing the main menu background world load and saving 5-15 seconds of load time (see the earlier callout in the DXVK section for full details). DXVK_ASYNC is intentionally absent -- it has no effect on any current Proton or Proton-GE release and was removed from GE-Proton in January 2023. If you are running a niche gplasync-patched build (wine-tkg or certain AUR packages), DXVK_ASYNC=1 may apply -- but standard GE-Proton and official Proton are not such builds, and this string is written for those.
On Linux, paths and environment variable names are case-sensitive. However, Arma 3's launch parameters (like -noLauncher, -noSplash, -skipIntro) are parsed by the Windows PE executable running inside Wine, which is case-insensitive for these flags. In practice, -nosplash and -noSplash are functionally identical. The consolidated string below uses the camelCase forms that match Bohemia's official startup parameters documentation for readability, but either casing works.
The -noLogs flag disables Arma 3's file-based logging, which eliminates continuous disk I/O during play that would otherwise compete with the streaming asset loads. It should be removed when troubleshooting crashes, mod conflicts, or unexpected behavior -- without logs, diagnosing these issues becomes significantly harder. The -noPause flag prevents the engine from halting simulation when focus leaves the window, which is useful in a multi-monitor setup. If you are using taskset to pin process affinity, add -cpuCount=N where N matches the number of physical cores in your taskset mask -- this is required as of 2.20 so the engine schedules its worker threads correctly. [source: Bohemia Interactive OPREP 2.20, dev.arma3.com]
PROTON_NO_ESYNC=1 PROTON_NO_FSYNC=0 WINEDLLOVERRIDES="xaudio2_7=n" MANGOHUD=0 gamemoderun %command% -noLauncher -noSplash -skipIntro -world=empty -noLogs -noPause
PROTON_NO_ESYNC=0 PROTON_NO_FSYNC=1 WINEDLLOVERRIDES="xaudio2_7=n" MANGOHUD=0 gamemoderun %command% -noLauncher -noSplash -skipIntro -world=empty -noLogs -noPause
Quick Reference: Sysctl File for Gaming
# Apply with: sudo sysctl --system
vm.max_map_count = 1048576
vm.swappiness = 10
vm.dirty_background_ratio = 5
vm.dirty_ratio = 20
These two parameters control when the kernel's background flusher threads begin writing dirty (modified, not-yet-flushed) memory pages to disk, and when the kernel forces a process to block until dirty pages are flushed. The Linux kernel documentation defines them precisely: "dirty_background_ratio: Contains, as a percentage of total available memory that contains free pages and reclaimable pages, the number of pages at which the background kernel flusher threads will start writeback." And "dirty_ratio: Contains, as a percentage of total available memory that contains free pages and reclaimable pages, the number of pages at which a process which is generating disk writes will itself start writing out dirty data." [source: kernel.org, /proc/sys/vm documentation]
The default values are vm.dirty_background_ratio = 10 and vm.dirty_ratio = 20. On a system with 64 GB of RAM, that means 6.4 GB of dirty pages can accumulate before background flushing starts, and 12.8 GB before processes are forced to block. When Arma 3's asset streaming generates bursty write traffic -- log output, save data, shader cache writes -- and the dirty page threshold is reached, the kernel can generate a sudden flush stall that interrupts the main simulation thread at exactly the wrong moment. Lowering dirty_background_ratio to 5 causes the background flusher to start earlier and in smaller batches, preventing the large one-time flush event that manifests as a frame spike. Keeping dirty_ratio at 20 ensures the process is not forced to block under ordinary load. This matters proportionally more on high-RAM systems (32 GB+) because the default percentage thresholds represent larger absolute dirty-page volumes. On a 16 GB system the defaults are generally fine; on a 64 GB system, 10% of 64 GB accumulating before background flushing kicks in is a genuine latency risk during asset streaming.
zswap/zram exception: on systems using compressed in-memory swap (common on Fedora and modern Ubuntu), this tuning is less critical since swap penalty is lower. But for write-to-disk dirty flushing behavior, these parameters remain relevant regardless of swap type.
Many online guides still include kernel.sched_latency_ns and kernel.sched_min_granularity_ns. These are CFS (Completely Fair Scheduler) tuning knobs. The Linux 6.6 kernel release notes at kernelnewbies.org add: "In this release, it is replaced by code that uses a new algorithm, called EEVDF... Many of these tunables have been removed." [source: kernelnewbies.org, Linux 6.6] The official kernel documentation confirms the transition: "The Linux kernel began transitioning to EEVDF in version 6.6 (as a new option in 2024), moving away from the earlier Completely Fair Scheduler (CFS)." [source: kernel.org, EEVDF Scheduler documentation] On Ubuntu 24.04, Fedora 39+, Arch, and any distribution shipping kernel 6.6+, these CFS parameters are silently ignored or simply absent from the sysctl namespace -- depending on the specific parameter and kernel build, you may see the value accepted but have no effect, or a "no such file or directory" result. Either way they do nothing useful. They are intentionally excluded from the sysctl reference above. vm.max_map_count, vm.swappiness, vm.dirty_background_ratio, and vm.dirty_ratio are unaffected and remain valid on all kernel versions.
Apply with sudo sysctl --system after creation. These settings persist across reboots and form the minimum kernel-level configuration baseline for running Arma 3 under Proton with any expectation of smooth frame delivery. The four parameters (vm.max_map_count, vm.swappiness, vm.dirty_background_ratio, and vm.dirty_ratio) are valid on all supported kernel versions and have no interaction with the EEVDF scheduler transition described in the warning above.
The Bigger Picture
Arma 3 is an extreme test case because its architectural bottlenecks are so narrow and well-defined. Every optimization in this guide addresses a real, measurable system behavior: TLB miss rates, synchronization overhead, memory allocation latency, shader compilation stalls, cache coherency, I/O scheduling. These are not magic tweaks -- they are interventions at specific points in the system stack where default Linux configuration does not match the demand profile of a simulation engine running through a compatibility layer.
It is worth being precise about the threading situation, because it is frequently misrepresented -- and the picture changed significantly with 2.20. The XenyxNZ guide, one of the more accurate community references, makes the foundational distinction clearly: Arma 3 "does utilize multithreading for many things" -- texture loading, file I/O, audio, and some rendering tasks do run on secondary threads. "However, it still suffers from severe performance bottlenecks in critical aspects due to limitations in its design." [source: XenyxNZ Arma 3 Performance Guide, GitHub] That was written before 2.20. As of 2.20, Bohemia's own programmer Dedmen has moved additional work -- explosion damage visibility checks, PhysX high-density object simulation, listbox processing, and more -- to parallel execution. The OPREP describes the outcome: "The frame times should feel more consistent, with fewer spikes... The average FPS, meanwhile, has only received a minor bump." [source: Bohemia Interactive OPREP Performance Optimizations in 2.20, dev.arma3.com] The simulation loop's scripted logic still runs on the main thread -- AI decision-making involving SQF scripts cannot be parallelized without breaking the scripting model. But the secondary threads now carry enough meaningful load that this guide's I/O scheduling, memory allocator, audio buffer, and kernel tuning all apply with greater force: keeping secondary threads fast prevents them from becoming bottlenecks that starve the main thread indirectly, and the penalty for starving them via aggressive affinity pinning is now higher than it was before 2.20.
One additional point with direct relevance to Linux users: the OPREP also confirmed that "Update 2.20 will be the last version of Arma 3 supporting its 32-bit application to a reasonable degree." [source: dev.arma3.com] If you are somehow still running the 32-bit build under Proton -- which can happen if Proton launches the wrong executable -- verify you are running arma3_x64.exe, not arma3.exe. The 32-bit build is frozen at 2.20 and receives no further updates. Under Proton, the correct binary is typically selected automatically, but confirming via ps aux | grep arma3 while the game is running takes seconds and rules out a silent regression that would make every other tuning effort partially or wholly irrelevant.
The underlying skills transfer broadly. Every technique described here applies to any latency-sensitive workload under Wine or Proton: the synchronization model insights apply to any multithreaded Windows application; the DXVK shader compilation understanding applies to any DirectX title; the kernel virtual memory tunables apply to any working-set-intensive process.
The situation with Arma 3 on Linux in 2026 is arguably the best it has ever been. Despite running through multiple translation layers, players on the Profiling branch performance binary with 2.20 frequently report performance competitive with or superior to Windows. The reasons most cited by the community: Vulkan's driver overhead profile is generally lower than DirectX 11's legacy driver model for Arma 3's draw call workload; the absence of Windows telemetry and background service overhead competing for simulation thread time; and an engine that is now actively distributing more work across cores than at any point in its twelve-year history. Whether the Vulkan overhead advantage holds for a given hardware configuration depends on driver maturity, GPU vendor, and workload type -- it is not a guarantee, and Bohemia has not published controlled Windows-vs-Linux benchmark data for 2.20. What is documented is the 2.20 OPREP's outcome on both platforms equally: fewer frame time spikes, more consistent frame delivery. The Linux tuning stack -- Proton, DXVK, FSync, mimalloc, kernel parameters -- is not a workaround. For this particular game and workload, it is increasingly a fully viable platform whose overhead, when properly tuned, competes on approximately even terms with the Windows equivalent.