Valve Turned On a Cache That Only Helps the Second Time You Play

By Indie Kings | September 28, 2026

Updated September 28, 2026: Valve has enabled the FEX code cache in Proton Experimental for ARM64. The announcement is a single sentence from developer Pierre-Loup Griffais on Bluesky, and three of its words carry the whole meaning: the cache should improve 1% low frame times, especially over subsequent runs. That is a claim about stutter consistency, not average frame rate, and it is a claim that the first run does not benefit. The FEX source confirms the asymmetry is a known property rather than a hedge, and that the project has been actively engineering it away with a mechanism that landed four months ago, is off by default, and is not implemented at all on Windows hosts.

Screenshot of the Bluesky post by Pierre-Loup Griffais announcing the FEX code cache is enabled on Proton Experimental for ARM64

Image: The announcement as posted. Credit: Pierre-Loup Griffais, via Bluesky, screenshot reproduced by GamingOnLinux.

The claim, and the three words that matter

Here is the entire announcement, and it is worth taking apart rather than paraphrasing, because almost every interesting property of this change is in the qualifiers.

"The FEX code cache is now enabled on Proton Experimental (ARM), and should exhibit improved 1%-low frame times, especially over subsequent runs."

Attributed to Pierre-Loup Griffais on Bluesky, and reported by GamingOnLinux on September 28, 2026. Griffais is a Valve developer, so this is a primary statement rather than a report, but it is a statement of intent rather than of results. Decomposed:

ElementWhat it commits toWhat it does not commit to
1% lowBetter frame time consistency, measured as the slowest 1% of framesAny improvement in average frame rate or throughput
shouldA prediction that the mechanism will helpAny measurement, number, test system or game
especially over subsequent runsImprovement that accumulates across runsAny benefit at all on the first run
(ARM)Only the ARM64 Proton buildAny change to x86 SteamOS, desktop or Deck-class hardware paths

The metric choice is the substantive one. A 1% low figure describes the tail of the frame time distribution, which is where stutter lives. A cache of translated code removes work that would otherwise be done at unpredictable moments during a session, so the expected shape of the benefit is exactly this: less work at the wrong time, which flattens the tail rather than raising the mean. Reading this as a performance increase would be a misreading. A user whose average frame rate is unchanged and whose stutter is reduced would see precisely this result, and a user whose average frame rate improves would not.

The hedge is honest rather than evasive. No benchmark accompanies the change, no game list is given, and there is no before and after figure. Writing it up as a speedup would be inventing a number the source does not contain.

What the code cache actually stores

To be clear about the mechanism, because the name suggests something narrower than it is.

FEX is a user-mode x86 and x86-64 emulator for Arm64 Linux. Its repository describes it exactly that way, and the project is FEX-Emu/FEX, MIT licensed, written in C++, first created March 6, 2020, with 8,177 stars and commits landing on the day this article was written. On a device whose processor is ARM64, running an x86 game through Proton means FEX translating x86 machine code into ARM64 machine code at runtime and then executing the result.

That translation is the expensive part, and the cache stores the output of it. As GamingOnLinux puts it, the code cache is "basically a place where FEX keeps the ARM64 machine code it generates after translating x86 code, so it doesn't have to keep doing the same translation work repeatedly." The improvement is real and it is not a trick: the same translation is simply not performed twice.

Two consequences follow that are worth stating plainly. First, the cache is specific to a particular binary, since it is a cache of that binary's translation. It is not a general optimisation and cannot be shared between games. Second, and more importantly for the announcement, a cache that only helps on later runs has to be written by an earlier run, and writing it costs time. The cache converts a per-session cost into a once-ever cost, and the announcement's careful wording is a direct consequence of that arithmetic.

Why the benefit is a curve, not a switch

The interesting finding is that the FEX project has already characterised this problem, fixed part of it, and documented the remainder. The announcement's hedge is not caution. It is a description of a known state of the code.

In the FEX repository, a pull request titled "CodeCache: Implement lazy code loading" was merged on May 21, 2026. Its description states the problem directly:

"Currently, FEX eagerly loads the entire contents of a code cache when the guest app loads the corresponding library. But typically, not all cache contents are needed in every single run (and especially not immediately after loading), so this not only increases memory use but also adds undesirable disk IO compared to an uncached run."

That last clause is the sentence that matters. Before this change, loading a code cache could cost more disk IO than running with no cache at all. The upstream project is stating that a cache was, in some conditions, a pessimisation. That is not a hypothetical risk being described in the abstract; it is a measured or at least observed property of the previous implementation, and it is the reason the merge happened.

So the announcement's "especially over subsequent runs" describes a curve with a known bad region at the start, and the FEX project has been working on that region since May. The realistic expectation from Griffais's sentence is that run one is neutral to slightly worse, runs two onward are better, and the size of the gain grows the more you play.

How FEX loads cached code lazily, and why with a segfault

The implementation detail in that pull request is genuinely interesting, and it explains the design rather than just describing it.

Instead of reading the whole cache into memory when a library loads, FEX now loads cached code pages on demand, and the mechanism is unusual: "Pages are now loaded from the cache file into memory on-demand as they're being executed (or otherwise accessed) by setting up a segfault handler on yet-untouched code pages."

A segfault as a control flow mechanism looks like a hack until you read the reason it was necessary, which is in the same pull request: "we can't rely on the OS's standard mechanism for memory-backed files here since FEX needs to apply its JIT relocations on loaded code sections." The normal trick for lazily faulting a file into memory is to map the file and let the kernel fault pages in. FEX cannot do that, because the cached code cannot be executed where it lands in the file. It has to be written into a separate protected code buffer, with the JIT relocations applied to it, before it can run.

The source confirms that arrangement. The comment in the cache loading path explains that "the host addresses will point into the protected code buffer, so that FEX can lazily apply relocations on first execution of each page." Two things are therefore happening: the cache file is mapped, and execution targets point somewhere else, and the per-page relocation work is deferred to the first time each page actually executes.

There is a second-order problem the code handles, and it is a nice piece of care. Pages are not independent. The routine that decides which pages to finalise will extend its range when a page has relocations or block link records that cross into the next page, and the stated reason is "to ensure we don't attempt to finalize any page twice, partially apply FEX relocations, or trigger page loads during block linking." It also opens with an explicit check for a race, noting it should first "check if we were racing another thread in loading this range" and returning an empty range if the pages are already loaded.

What is not finished

Two caveats in the source are worth carrying, because they bound what the change can currently deliver.

The first is that the fix for the first-run problem is disabled by default, and its setting name says so. The pull request states: "This new behavior is off by default and controlled by a new setting called EnableLazyCodeCachingWIP." The WIP is in the name. The mechanism that addresses the disk IO penalty was merged in May 2026 and is still not on.

The second is that the mechanism does not exist on all platforms FEX builds for. The cache loading path in the source is split, and on Windows hosts the lazy branch is replaced entirely:

The Linux path finalises pages eagerly when lazy caching is off, or when code cache validation is on. The Windows path carries a comment reading TODO: Implement lazy mapping on Windows and finalises every page regardless of settings. So on a Windows host FEX always does the eager thing, and the flag has no effect.

Since Proton on Linux is the SteamOS case, that split is not a problem for the platforms this change is aimed at. It is a problem for anyone reasoning about FEX as a general emulator, and it is the reason the improvement cannot be described as universal.

There is also an unfinished item in the save path. A pull request titled "CodeCache: Accept multiple threads in SaveData" was opened on May 17, 2026 and last updated on May 22, 2026, and it remains open. Its entire description reads: "Needs some consideration of if this is the cleanest way, but this is certainly the simplest." Four months have passed with no further activity. So the code cache save path does not yet accept multiple threads, and that has been sitting open since around the time the lazy loading work landed.

The security question, and the tool that already answers it

The most substantive thing in the coverage of this change is not in the article. It is in a comment, and it raises a real problem with an obvious answer that already exists in the FEX codebase.

On the GamingOnLinux thread, a reader asks: "I wonder if they will put in place some infrastructure to share the cache among multiple users, like they do for shaders. It is a bit more tricky to secure correctly (I could submit arbitrary code, and boom, you get an RCE vulnerability), but I think it could be done with a few server-side checks. Or even perform server-side translation and have clients submit the original x86 opcodes."

The security analysis there is correct and important, and it is worth restating precisely because the reason is structural. A code cache entry is not data about a program. It is generated machine code. Any mechanism that accepts a cache entry from a client and runs it is accepting arbitrary code execution by construction, and no amount of validation on the surrounding metadata changes that. The same is not true of shader caches in the same way, because a cached shader is at least nominally a description of a computation rather than an instruction stream, and because the blast radius of a malformed shader is a crash. Here the blast radius is everything the process can reach.

The commenter's own proposed fix is the right one: "perform server-side translation and have clients submit the original x86 opcodes." And FEX already contains a tool that does exactly that, which the comment does not mention and the coverage does not either.

It is called FEXOfflineCompiler, it lives in the FEX repository under Source/Tools/FEXOfflineCompiler, and it takes two subcommands. The first is generate, documented as "Trigger cache generation from combined code map." The second is process-all, documented as "Process all new code maps and update all caches." It also accepts an --outdir option described as "Output directory for generated cache files" and an --fileid option described as "Select binary to generate cache for."

process-all is a batch job over pending code maps that produces cache files as output. That is server-side translation as a shipped component, taking untrusted input and producing a trusted artefact, with the client receiving the generated code rather than submitting it. Whether Valve ends up distributing FEX caches this way is not stated anywhere, and it would be a mistake to assert it. What can be said is that the design question the commenter raised has an existing answer in the project, and that answer is a batch compiler rather than a shared cache.

What this covers, and what nobody has measured

Sweeping up the boundaries, because several of them are wider than the announcement suggests and one of them is empty.

On platform scope, the ARM qualifier is narrower than the coverage implies and slightly wider than the announcement implies. The build affected is the ARM64 Proton build, which is the one used on ARM64 SteamOS devices. That is the Steam Frame, and it is also the Steam Deck, since the Deck's custom SoC is also ARM64. The x86 SteamOS paths used on desktops and the Steam Machine are not touched by this change at all. Reporting it as a Steam Frame feature understates who gets it; reporting it as a general Proton speedup overstates it.

On measurement, the announcement contains none, and this is the real gap. There is no figure for how much 1% low improves, no game tested, no indication of how large a typical cache is, no statement of how much the first run costs relative to an uncached run, and no comparison against a build with the cache disabled. The FEX pull request that characterises the first-run problem suggests the upstream team cared about that specific number, and did not publish it in the announcement.

On timing, the cache is in Proton Experimental, which is a test channel, and no date has been given for SteamOS stable. On durability, the FEX project is healthy and actively developed, with commits on the day of writing, so the risk here is not abandonment. On granularity, the code cache is per-binary, so a game update invalidates it and the first run after every patch starts the curve again. That last point is the practical one for a player, and it is the reason a benefit described as accumulating across runs will accumulate unevenly for a game that updates itself regularly.

FAQ

What exactly did Valve announce?

One sentence, from developer Pierre-Loup Griffais on Bluesky: the FEX code cache is now enabled in Proton Experimental for ARM64 and "should exhibit improved 1%-low frame times, especially over subsequent runs." No benchmarks, game list or test system accompanied it. It is a primary statement because Griffais is a Valve developer, but it is a statement of intent rather than of measured results.

Will this raise my average frame rate?

Not according to the claim, which is specifically about 1% low frame times, meaning the slowest 1% of frames. That is the tail of the frame time distribution, which is where stutter shows up. A cache removes translation work that would otherwise happen at unpredictable moments in a session, so the expected shape of the benefit is flatter consistency rather than a higher mean. A user whose stutter improves and whose average frame rate does not would be seeing exactly the promised result.

Why does it only help on later runs?

Because the cache has to be written before it can be read, and writing it is the cost the cache is designed to avoid paying twice. The FEX project's own documentation of a merged improvement states that the previous eager behaviour "adds undesirable disk IO compared to an uncached run", which is an admission that a cache could be slower than no cache on a first run. Griffais's "especially over subsequent runs" is describing that known curve rather than hedging.

Is the code cache shared between users, like shader caches?

Nothing has been announced either way. A comment on the coverage raises it, and correctly identifies the danger: a cache entry is generated machine code, so accepting one from a client is arbitrary code execution by construction. The same comment proposes the mitigation of server-side translation with clients submitting original x86 opcodes, and FEX already ships a batch tool for that purpose, FEXOfflineCompiler, with generate and process-all subcommands. Whether Valve uses it this way is not stated.

Which devices does this affect?

Only the ARM64 Proton build, since the announcement specifies (ARM). That covers ARM64 SteamOS devices, which means both the Steam Frame and the Steam Deck. The x86 SteamOS builds used on desktops and the Steam Machine are unaffected. The effect is per-binary as well, so a game update produces a new cache and restarts the improvement.

What is still unfinished in FEX?

Three things. The lazy code loading that addresses the first-run disk IO penalty was merged on May 21, 2026 but is off by default behind a setting named EnableLazyCodeCachingWIP, so the WIP is in the name. It is also not implemented on Windows hosts, where the source carries a TODO for lazy mapping and finalises every page regardless of settings. And a pull request to make the cache save path accept multiple threads, opened May 17, 2026, is still open and has seen no activity since May 22.

Bottom Line

Strip this announcement down and it is a small, careful, unmeasured change with a precise scope, and the precision is the interesting part rather than a limitation of the reporting. Valve's developer said the FEX code cache is live in the ARM64 Proton build and should improve 1% low frame times, especially over subsequent runs. Three qualifiers, and each one is load bearing. The metric is the tail of the frame time distribution rather than the mean, so the target is stutter and the promise is not extra frames per second. The verb is "should", with no benchmark attached, so there is nothing here to compare against. And "subsequent runs" is a statement that the first one does not help, which turns out to be the most honest and most technical part of the sentence.

Because the FEX source is public, that last point could be checked rather than taken on trust, and the check changes how the sentence should be read. A pull request merged on May 21, 2026 states that FEX used to load an entire code cache when a library loaded, and that doing so "adds undesirable disk IO compared to an uncached run". That is the upstream project documenting a case where having a cache was worse than having none. So the first-run cost Griffais is hedging about is not a marketing caution, it is a measured property of the previous implementation, and the reason the improvement accumulates across runs is that a per-session cost has been converted into a once-ever cost. A player who updates a game often will see that curve restart more than once, which is a practical consequence nobody has written down.

The engineering answer to the first-run problem is also the most interesting thing in the FEX codebase this cycle, because the obvious approach does not work here. FEX cannot rely on the operating system's standard mechanism for lazily mapping a file into memory, and the reason given is that it "needs to apply its JIT relocations on loaded code sections". Cached code cannot be executed where it sits in the file, so it has to land in a separate protected buffer with relocations applied before it runs. What FEX does instead is set up a segfault handler on code pages that have not been touched, so the fault itself becomes the signal to load and relocate that page. It is a fault used deliberately as control flow, which is the correct call given that the normal path is unavailable. The code also handles the fact that pages are not independent, extending a range when relocations or block link records cross a page boundary, specifically to avoid finalising a page twice or applying relocations partially, and it opens with a check for the case where another thread got there first.

What is not done is as informative as what is. The lazy loading merged in May is off by default, behind a setting called EnableLazyCodeCachingWIP, so the fix for the very problem the announcement hedges about is present and disabled. It is not implemented on Windows hosts at all, where the source carries a TODO and finalises every page unconditionally, which is harmless for the SteamOS case but means the improvement cannot be described as universal. And a pull request to let the cache save path accept multiple threads, opened in May, has been open and untouched for four months, its entire description being that the approach is possibly not the cleanest but is certainly the simplest. An emulator project with 8,177 stars and commits landing today is not struggling, so the gaps are ordinary backlog rather than a warning sign.

The genuinely underreported part of this story is a comment. A reader on the coverage asked whether Valve would share code caches between users the way it shares shaders, and immediately identified why that is harder here: a cache entry is generated machine code, so a client that can submit one can achieve arbitrary code execution, and the blast radius is the whole process rather than a crashed shader. The analysis is right for a structural reason, and the commenter's own proposed fix was the right one, namely server-side translation with clients submitting original opcodes. FEX already ships exactly that, as a tool called FEXOfflineCompiler with a generate mode for a single code map and a process-all mode that batches new maps into updated caches, taking an output directory and a binary id. Whether Valve distributes caches that way is not stated and should not be assumed. What can be said is that the hard question has a shipped answer in the project already, and it is a batch compiler rather than a shared cache.

So: not a speedup, a consistency improvement, scoped to ARM64 SteamOS hardware including the Deck as well as the Frame, per-binary rather than global, cumulative rather than immediate, and unmeasured at the point of announcement. The reporting that exists has it roughly right. What it missed is that the first-run caveat is documented upstream, that the fix for that caveat is merged and disabled, and that the hardest question about sharing these caches was asked and answered in a comment thread the same day.

Source: The announcement is a Bluesky post by Pierre-Loup Griffais, quoted verbatim in the GamingOnLinux report of September 28, 2026, which is also the source for the plain-language explanation of what a code cache is and for the reader comment on sharing caches. The FEX repository is the primary source for the project description, licence, language, creation date, star count and activity, and for the following, read from source rather than reported: the existence of Source/Tools/FEXOfflineCompiler with its generate and process-all subcommands and their help text, the --outdir and --fileid options, the split between the Linux and Windows cache loading paths including the Windows TODO for lazy mapping, the comment that host addresses point into the protected code buffer so relocations are applied on first execution of each page, the range extension logic for pages with relocations or block link records crossing a boundary, and the thread race check at the start of page finalisation. FEX pull request 5441, CodeCache: Implement lazy code loading, merged May 21, 2026, is the source for the eager loading behaviour, the disk IO cost relative to an uncached run, the segfault handler mechanism, the inability to use the operating system's standard mechanism because JIT relocations must be applied, and the off-by-default EnableLazyCodeCachingWIP setting. FEX pull request 5500, CodeCache: Accept multiple threads in SaveData is the source for the open status, dates and description quoted. The claim that the benefit targets stutter rather than average frame rate is this article's reading of the 1% low metric, and the observation that a per-binary cache restarts the accumulation after a game update follows from the cache being keyed to a specific binary. No performance figure is claimed anywhere in this article, because none has been published

Related: Proton Experimental Fixes the CEG Regression and Adds Early 32-Bit OpenXR Support | Proton Experimental Adds Wine Mono 11.3 and Xalia 0.5 With Arm64 Builds | Valve Steam Frame Engineering Deep-Dive: Water Cooling, Thermals, Power, Acoustics | Valve's Steam Frame VR Headset - First Look | Valve Steam Machine, Desktop SteamOS, Steam Frame VR, & Controller | SteamOS 3.8.28 Stable Adds VRAM Management and Handheld Support

Share