Valheim
Install with App

Details

Latest version
1.0.27
Last Updated
First Uploaded
Downloads
3.5K
Likes
4
Size
96KB
Dependants
ADDatHost Valheim hosting
30% off

ValPerfPatcher

Runtime fixes for performance hot spots found with ValPerfMonitor. It patches other mods in memory at startup. It never edits their files, and each mod keeps working the same way, minus the wasted work. A few fixes are not speed-ups: the map icon order, shared inventory favourites, BetterMap errors after a relog, and a few that only remove glitches and errors.

Fixes

Each table is sorted by size of gain, biggest first.

Valheim, Jotunn, BepInEx and code shared by many mods

Fix Applies to Problem Change
Write each mod config file once at startup and world load BepInEx (any mod adding many settings: RtD Ocean, Creature Level & Loot Control, AzuExtendedPlayerInventory, Comfort Tweaks and others) BepInEx rewrites a mod's whole config file after every new setting it adds; mods adding hundreds while they start wrote their files thousands of times (RtD Ocean: 8.5 s for its 127 KB file), and some do it on a world load (Comfort Tweaks: about 260 saves of a 52 KB file, 1.5 s) Until the main menu has started, and while a world loads until your character appears or your game connects to a server (so a value sent by the server is never written to your file), those saves are held and each file is written once (4,774 saves became 59 in a 70-mod profile at startup, about 450 became 5-7 on a world load). Same files. Mods loading 26.3 s -> 13.3 s, launch to main menu 44 s -> 28 s.
Keep a fast-loading copy of mod asset bundles Any mod with LZMA asset bundles (RtD Ocean, More World Locations, Bestiary, Valheim Armory, OdinShip and others) Unity unpacks an LZMA bundle completely every time it loads it (about 8 s at every startup with those five; More World Locations' location files whenever they load) The first load converts a copy to LZ4 in the background (BepInEx\cache\ValPerfPatcher); later loads use the copy. 24.8 s -> 15.7 s loading mods. Works whichever way a mod loads its bundles. Same assets; a mod update, or a game update that changes Unity, gets a new copy; copies unused for 30 days are deleted.
Read the translation files once Valheim itself Mods make their own English translation objects, and each one re-reads all 13 of the game's translation files (every language column, character by character): about 0.4 s each time, 9 s at startup with 62 mods The words each file gives per language are kept after the first read and added instead. Mods' own translation hooks still run, so the words are the same. Starts before every other mod (a second small plugin, "ValPerfPatcher (early start)"), so the loads made while mods start are covered.
Reuse loaded mod translations Mods bundling LocalizationManager (AAABuildMenu, Bestiary, GapFiller, Seasonality, HearthBelow, Comfort Tweaks, Sailing, the Azu mods and others) Every time any translation object is set up, each copy searches the whole BepInEx folder and re-reads its translation files; some do it once per game translation file After its first load per language, each copy adds its already loaded words.
Biome map on all CPU cores Valheim itself (any world; Riverheim 1.2.1 worlds too) Every world load the game looks up the biome and height of 4 million map points one at a time: about 2.8 s on a normal world, about 5 s with Riverheim The same lookups split across CPU cores; each point is still filled once, so the map is the same (checked point by point on both kinds of world). Measured on 32 threads: 2.8 s -> 0.95 s on a normal world, 5 s -> 0.3 s with Riverheim. Only while no other mod hooks those lookups.
Free unused path-finding tiles sooner near the link limit Valheim itself Path-finding joins its area tiles with links (about 90 per tile) and Unity allows only 65,535. Travelling fast past coasts full of creatures (sailing, portals) kept enough tiles loaded to reach that (50,000 links in 542 tiles measured), after which every new link fails and the game logs an error each time (44,000 errors in one sailing trip) About once a second the number of links is checked; once it is above 45,000, and until it is back under 40,000, tiles nobody has used for 10 s are dropped instead of after the game's 60 s. Tiles in use are never dropped; otherwise nothing changes. Steps aside for Valheim Community Patch 0.37+, which has the same fix.
Inventory borders without garbage Mods drawing on inventory slots (AzuAutoStore 3.1.7, Recycle_N_Reclaim 1.4.5 and others) While the inventory is open, they look up their picture on every slot again each frame with a search that makes garbage: about 35 KB and 13 KB per frame for those two, the most of any mod in a live session, and the collections it set off froze the game for about 200 ms Every mod's inventory slot hook is checked once mods have started; the child found under each slot is remembered and checked, and the search runs only for a new or rebuilt slot. Same results.
Reuse gamepad button names Jotunn 2.30.2 "Joy!" + name builds a new string on about 80 input checks per frame (~2.4 KB per frame) One cached string per button
Distance check without garbage Valheim itself The "is this near the player?" check builds a throwaway 3-number array on every call (about 50 bytes, Mathf.Max with three values). The object ownership pass runs it for every nearby object every 2 seconds (~640 KB each time), and building pieces, rocks, spawners and object loading use it too Same result computed without the array. Measured: that pass went from 643 KB to under 5 KB, about one garbage collection per minute fewer standing still. Valheim Community Patch 0.35 and later has the same fix; with it installed this one stands down.
Throttle static object ground checks Valheim itself Every rock, stump and similar object casts a ray to check for ground under it about every 0.3 s, forever (about 85 per frame) After a check that leaves the object where it is, check again after about 2 s (setting Static object ground check interval, 0 = game default). An object can take up to that long to start falling after the ground under it is dug away. Works the same with Valheim Community Patch.
Floating objects sent less often Valheim itself (multiplayer) Floating physics moves a crate, log or dropped item in the water a little on every frame, and the game sends it to the server again each time: 20-26 times a second per object. A dozen floating cargo crates, logs and dropped items near a base were most of one player's 198 KB/s upload, and the server passes it on to everyone nearby Your game sends a floating object again only once it has moved 5 cm (setting Floating object sync step cm), turned 2 degrees or changed speed by 0.1 m/s from what it last sent. Measured: upload near those crates went from 198 KB/s to 34 KB/s. Creatures, ships, carts and fish are not affected. Works alongside Valheim Community Patch's creature version of this. Each player needs it for the objects their own game runs.
Creature walk and turn speeds sent less often Valheim itself (multiplayer) A creature's walking, sideways and turning speed (for its animation) wobble slightly on every frame, and each change sends the creature to the server again, about 10 times a second even while it stands around Those speeds are sent again only once they have changed by 0.1; a speed that close to zero is sent as zero. Players are not affected, and nothing changes on your own screen.
No stacked copies in new areas Mod vegetation (RtD Ocean 2.2.51) Some mod vegetation asks for groups of plants with no spread, so the extra plants of a group land exactly on the first: invisible copies that cost as much as real ones and stay in the world. RtD Ocean's seaweed, sponges, coral stones and crops do this (about a third of its seaweed near the coast) In newly generated areas, one plant per group. The game's own vegetation and areas already generated are not changed.
Remove stacked mod plants (off by default) Mod vegetation (RtD Ocean 2.3.1) Areas generated before the fix above, or on a server without it, keep their stacked copies for good (about 2,000 loaded at one busy base) When switched on (World: remove stacked mod plants), the extra copy on a spot is deleted from the world once your game owns that area (the zones around you, up to 50 a second); one plant stays. Changes the world, also on a server. Never the game's own plants, pickable plants or anything built.
Reuse creature spawn entries Mods bundling CreatureManager (Bestiary 1.2.1, Air Animals, Land Animals) Every time an area loads, the mod takes its creatures out of the shared spawn list and builds them again from its config, with reflection per creature (about 0.35 ms per area with Bestiary) Built once, then reused while they are still in the list and none of that mod's settings has changed. A setting change (also one synced from a server) rebuilds them at the next area, as before.
Character save without the map freeze Valheim itself Every character save packs the explored map one pixel at a time (2048 x 2048, twice) and compresses it on the game's thread: about 105 ms of a ~135 ms freeze. A server makes every player save on its 30-minute world save, and each game also saves on its own 30-minute timer For those two saves the map is copied in under a millisecond and packed on another CPU thread into exactly the same data, read back piece by piece the way the game's loader reads it (about 1 MB of garbage instead of the game's 43 MB), then the game's own save runs with it a moment later (about 30 ms left on the game's thread). An unchanged map reuses the last data. Logging out, quitting, sleeping and the save command save as before. Only the character file is involved, never the world. Steps aside if another mod changes the map save.
No log flood when standing still on ice Valheim itself A character standing still on slippery ground is turned toward where it has been sliding; right after moving straight up or down that direction is zero, and Unity logs "Look rotation viewing vector is zero" every physics step (about 50 a second) It keeps facing the way it does until it actually slides.
Other players on top Valheim itself (and TargetPortal 1.2.7) The map draws icons in the order they were made, so another player's icon is often hidden under pins made after they joined (locations, mod pins, pins scrolling into view) Other players' icons are moved to the top of the big map and minimap whenever the pins update (only when the order is wrong). While TargetPortal's portal map is open, its portal icons stay above the players (works alongside BossGatedPortals, which does the same for portal icons). Not a performance fix; switch it off with Map: other players on top. If it ever hits an error it switches itself off with one log warning.
Hide fish stuck in the air Valheim itself (multiplayer) A fish left alone stays where it was, often mid-jump. When players return it is handed to one of them before their game has loaded it, and everyone else sees it hanging above the water (common when sailing together) A fish another player owns that hasn't moved for 3 s, is above the water and isn't lying on anything is not drawn until it moves. Only your view changes. Not a performance fix; switch off with Base game: hide fish stuck in the air.
Snow cap on objects Valheim itself (and Seasonality's winter snow) The game's snow on pieces can grow about 1.3 m tall. On roofs and walls that looks right, but on small objects (beehives, smelters, kilns) it becomes a tall pillar, and Seasonality brings every unroofed piece to full snow in winter Snow on objects (crafting stations, furniture, chests, stacks and piles) is drawn at most 10% of full height (settings under Snow: the percentage, objects / listed pieces / all pieces, extra pieces, and whether the Deep North is capped too, off by default). Building parts keep the game's snow. Only your view changes. Not a performance fix.
Shared favourites Mods with the same favouriting code (Quick Stack - Store - Sort - Trash - Restock 1.4.16, AzuAutoStore 3.1.7, AzuExtendedPlayerInventory 2.6.4 and others) Each reads the favourites only when you log in and then keeps its own copy, so starring or unstarring through one is not seen by the others until the next login: the border of an item you logged in with seems stuck, and AzuAutoStore puts a newly starred item into chests All of them use the same favourites per character, combining what each had, so a change through any of them shows at once everywhere. Trash marks stay separate. Not a performance fix; switch off with Inventory: shared favourites.

Single mods

Fix Target mod Problem Change
Start building the world early Riverheim 1.2.1 Riverheim builds its whole world map (about 7 s on 32 threads) only after the game's 1.5 s start pause and the scene load The same build starts in the background the moment a world is started from the menu (or a dedicated server starts); Riverheim takes its results when it gets there. When joining a server, the world that server had last time starts building the moment you press Join, during the scene load (about 4 s less on the loading screen in a test); it is only used if the server sends the same world and settings, otherwise Riverheim builds as usual.
Count smelters without searching everything AutomaticFuel 1.5.5 (fixed in 1.5.7) Every time a smelter, kiln or furnace is created (entering a base, loading an area, the hammer's placement preview of one) it searches every object Unity has loaded, assets included, to count the smelters (about 35 ms each time with a big mod list) One full search per world, then the list is kept up to date as smelters are created and removed. Same count. Measured: 35 ms -> 0.015 ms per smelter.
Anyone can switch a torch AutomaticFuel 1.5.8 - 1.6.0 Its torch switch key only works for the player who owns the fire; others get "Cannot toggle (not owner)" Takes ownership of the fire first, as the mod already does for smelters. Not a performance fix.
Forget map boats when leaving a world BetterMap 1.1.0 It keeps the boats and carts shown on the map in a list that survives logging out. The game renumbers its objects for every world session, so after logging out and back in without restarting it fails to find an old boat and logs an error on every frame (11,000 errors in 3 minutes) The list is emptied when a world starts; BetterMap adds the boats again on its next update.
Ship check without garbage Longship Upgrades 1.0.24 Its mast speed limit checks every ship on every physics step (50 a second) and tells a longship by reading and trimming the ship's name, two new strings each time (about 5 KB a second per ship nearby) Tells a longship by the ship's saved type number instead. Same result.
Helpers added once after a relog Seasonality 3.8.4 On every world load it adds another fall-colour helper to each tree prefab and another frozen-water helper to the area template, and those live for the whole game session: after each logout and login every tree repeats the same colour work once more and every water area gets one more collider and material copy (about 12,700 more objects per relog) Added only when the prefab has none yet, as on the first world load.
Winter snow on every unroofed piece Seasonality 3.8.5 It clears the winter snow from any piece the game thinks has a roof. The game's roof check looks straight up from the piece but also counts the floor the piece stands on when it sits slightly sunk into it, so identical kilns side by side on a stone floor got snow or none by chance For winter snow only what is really above the piece counts, checked every few seconds instead of every second. The game's own roof check (rain wear, Deep North snow) is unchanged. Not a performance fix.
No error for the planting preview Farming 2.2.3 Every seed planted makes the game rebuild the see-through planting preview, and its plant hook then tries to save the farming skill on that preview, which has no save data: one NullReferenceException with a full stack trace per seed (473 in one 2.5 h session), a small stutter each time Its hook is skipped for objects without save data (the preview); real plants get the skill as before.
Item data added once after a relog Creature Level & Loot Control 5.0.7 On every world load it adds another original-item-values record to every item (about 12,500 more objects per relog); it only ever reads the first one Added only when the item has none yet, as on the first world load.
Forget old inventories MultiUserChest 0.6.2 It keeps a slot-block record for every inventory it has seen and never removes one, so every chest it has touched stays in memory with all its items, after the chest unloads and in later world sessions (+5,600 objects in 15 minutes of farming with auto-store) The list is emptied when a world starts, and while playing the records with nothing blocked are dropped once there are more than 64; the mod makes a fresh record whenever it needs one.
Map copies only when level circles are shown Creature Level & Loot Control 5.0.7 On every world load it keeps three copies of the whole map (192 MB for the session) and redraws the map for its distance circles, even when the circles are off Copies and redraw only while the circles are shown; switching them on later makes the copies then.
Vanity item lists kept from growing AzuExtendedPlayerInventory 2.6.4 Its vanity code remembers every item type it looks at. MultiUserChest and AzuAutoStore give each item they move its own copy of its type, so the list grew without end while farming (+42,800 objects in 15 minutes) Emptied when it passes 2,048 entries; anything still needed is worked out again.
Character preview only for your own gear AzuExtendedPlayerInventory 2.6.4 Re-renders its character preview (~1.1 ms) whenever any creature equips or unequips something, e.g. every monster that spawns with a weapon (1,368 times in 8 minutes at a base) Refreshed only when your own character equips or unequips.
Forget unloaded containers Quick Stack - Store - Sort - Trash - Restock 1.4.16 It keeps every container it has seen in a list and only removes one when it is broken, so containers in areas you left, and in earlier world sessions, stay for good and every quick stack walks past them (184 entries for 46 real containers after 3 relogs) Unloaded containers are removed just before each search. Same containers found.
Cache backpack check AzuExtendedPlayerInventory 2.6.1 Its Adventure Backpacks compatibility asks "is this a backpack?" several times per frame, and each answer allocates (~2.5 KB per frame) Remember the answer per item type, only once in a world
Item backpack check without garbage Adventure Backpacks 2.3.4 Its "is this item a backpack?" check, asked for every equipped item thousands of times a minute, compares the item's prefab name with each backpack type through a new lambda each time, and "is a backpack equipped?" also builds a new list of equipped items (about 3 KB per check without a backpack on, ~2 KB per frame at a busy base) Remember the answer per item type (an item carrying its own backpack data still counts), and look through the equipped items without the new list. Same answers. Adventure Backpacks 2.4.6 does the equipped-items part itself, so there only the per-type answer is used.
Name trader pins only when the map updates them BetterMap 1.1.0 To name the trader pins it looks up every location on the map on every frame (~0.1 ms and ~0.8 KB per frame), although the game only adds or removes location pins every 5 seconds Runs only on the frames where the game updated those pins. Same pins, same names.
Skip idle zone sorting HearthBelow 0.6.2 Every frame it sorts all terrain zones with a new closure, even when no zone has work (~1 KB of garbage per frame) On frames with no queued zone work, run only the distant-terrain and visibility updates. Frames with work run the original unchanged.
Reuse dig preview fit HearthBelow 0.6.2 While a digging tool is held, the dig preview fits its rings to the ground every frame: up to 240 raycasts (about 0.2 ms per frame) While the aim stays put (within 1 cm), reuse the last fit for up to 0.2 s. The preview and its settings are unchanged.
Minimap level text a few times per second Creature Level & Loot Control 5.0.6 Rebuilds the minimap's area level text every frame (a new dictionary, text replace, number text and a copy of the level thresholds, about 0.6 KB per frame) Rebuilt at most every 0.25 s (setting Minimap level refresh seconds); the first update after the minimap opens runs at once. The shown level can lag by up to that long.
Server time asked less often Creature Level & Loot Control 5.0.7 Every inventory teleport check (portals every 0.5 s nearby, backpacks on every save and load) asks the server for its time over the network, up to about 100 times a second (240,604 requests in one session) Asked at most every 10 seconds per server connection; the mod keeps that time moving forward itself in between. Only on clients joining a server.
Backpack check without garbage Adventure Backpacks 2.2.9 Its patches on the game's ownership check and network calls read every object's name (gameObject.name, a new string each time) to spot its hidden backpack object: about 56 bytes on each of hundreds of checks per frame The name is read once per object and the answer remembered
Reuse permission section name Server Devcommands 1.115 Every permission check (several per frame) lowercases the same section name into a new string Lowercase it once and reuse it

Measured for the HearthBelow zone sorting, AzuExtendedPlayerInventory and Jotunn fixes (ValPerfMonitor, same spot, with and without): the garbage those three mods create dropped from 5.4 to 1.2 KB per frame, and garbage collections from 12 to 9 per minute. The gain is less garbage collection (fewer small stutters), not CPU time: the HearthBelow fix's CPU saving was within run-to-run noise.

Measured for the in-game fixes (62 mods, standing still in the Meadows, same spot, all fixes on vs all off):

Off On
Garbage created by mods, per frame 12.2 KB 5.4 KB
Garbage collections per minute 9 7
CPU time in mods, per frame 0.71 ms 0.55-0.62 ms

Startup and world loading (same 62 mods): 54.1 s to the main menu with the translation fixes off, 41.1 s with them on; the fast-loading asset bundle copies then cut loading mods from 24.8 s to 15.7 s. World loading on a Riverheim world: 23.3 s -> 14.4 s until the player spawns.

Safety

Every fix:

  • only applies when its target mod is installed (the base-game fixes always apply);
  • checks that the target code still looks the way it was analysed, and stands down with a log warning if a mod update changed it;
  • steps aside when another installed mod already does the same job as well or better (the log says which), and works as before without it;
  • keeps the target mod's behaviour the same, apart from the short delays the table mentions (ground checks, minimap level text, the regular character save finishing a moment later), path-finding tiles going sooner while the link limit is close, Map: other players on top, which only changes which map icon is drawn on top, Inventory: shared favourites, which makes the favourites mods agree, and the fixes that only remove glitches and errors;
  • can be turned off on its own in BepInEx/config/valheimmods.ValPerfPatcher.cfg.

The startup log says which fixes were applied, for example HearthBelow: skip idle zone sorting: applied.

Requirements

  • Valheim 1.0 with BepInExPack_Valheim 5.4.23 or newer.
  • Only needed on the PCs where you want the speed-up (or the map change). Servers and other players don't need it, and it sends nothing over the network. None of the target mods is required: fixes for mods that aren't installed do nothing.

Installation

Gale / r2modman: Import → local mod and pick the release zip. Manual: copy ValPerfPatcher.dll to BepInEx/plugins/ValPerfPatcher/.

Checking that it helps

Install ValPerfMonitor in a test profile and run its full suite (Shift+F11) twice at the same spot: once with this mod's fixes on, once with them all set to false in the config. Compare the runs in BepInEx/ValPerfMonitor/compare.html (Allocations and GC per minute show the difference).

Build from source

Needs the .NET SDK, Valheim, and a BepInEx profile that has HearthBelow installed (the HearthBelow fix compiles against its DLL; at runtime it's only touched when HearthBelow is present).

dotnet build -c Release
dotnet build -c Release -p:Deploy=true
python package.py
  • The first builds bin/Release/net48/ValPerfPatcher.dll.
  • Deploy=true also copies it into the profile's BepInEx/plugins/ValPerfPatcher/.
  • package.py (Python 3) makes the Thunderstore-format zip in dist/.

Paths default to a Steam install and a Gale profile named "Default". Override them: dotnet build -c Release -p:ValheimDir="D:\Games\Valheim" -p:GaleProfile="C:\path\to\profile" (the folder that contains BepInEx). HearthBelow is expected at BepInEx/plugins/Azumatt-HearthBelow/HearthBelow.dll. It targets .NET Framework 4.8 because HearthBelow does.

Adding a fix

Each fix is one file in src/ with an Apply(Harmony, ...) method that returns null when applied or a reason when it stood down, registered in Plugin.cs with its own config switch. Before patching, verify every member it touches exists with the expected type; if not, return a reason instead of patching.

License

MIT, see LICENSE.

Thunderstore development is made possible with ads. Please consider making an exception to your adblock.