← Back to KnowledgeWhy Garry's Mod Won't Scale on Threads: The Single-Threaded Core and What It Means for Tick Rate

Garry’s Mod server owners under load reach for the same fix almost every time: more CPU, more cores, a bigger instance. That instinct is understandable and, for this engine, frequently wrong. The Source engine’s server loop - the loop GMod runs on top of - is not multi-threaded. This is confirmed independently across multiple hosting providers’ own optimization documentation, and it’s the standard explanation given for why heavy addon stacks (Wiremod, ACF, complex DarkRP or roleplay frameworks) cause server-wide lag regardless of how much hardware headroom is available. A second core sitting idle doesn’t help a bottleneck that’s structurally confined to one.

The counterintuitive fix: lower the tick rate, not raise it

Because the loop is single-threaded, tick rate is really a budget: how much work has to fit inside one core’s time slice, N times per second. Push that number too high and you’re asking a single core to do more work than it can finish in time, every tick. Independent hosting-provider documentation converges on the same finding: pushing tick rate too high causes problems on typical hardware well before it delivers a proportional benefit (the Facepunch wiki’s own -tickrate guidance recommends staying within a 66-128 range), and the standard remedy for lag at high player counts is lowering tick rate, not raising it.

Many heavy Garry’s Mod servers - DarkRP and other prop-heavy roleplay frameworks especially - deliberately run tick rate well below Source’s native default of roughly 66, often capping around 33. That’s not because Sandbox itself defaults there - GMod’s default tickrate is Source’s own ~66.67, same as any other gamemode - it’s a deliberate operator choice: prop physics simulation doesn’t scale linearly with tick rate, so a heavier server buys headroom by capping it manually. There’s no publicly benchmarked curve for exactly how physics cost scales with prop count and tick rate, so we won’t invent one, but the commonly cited starting points for a heavier server are worth knowing: sbox_maxprops around 100 per player, gmod_physiterations at 4, and sv_turbophysics enabled.

Running tick rate too low has its own visible symptoms worth recognizing during triage: wobbly vehicle suspension, “moondrive” (wheels visibly turning slower than they should), and reduced grip or acceleration on vehicle addons. Tuning tick rate is a balance, not a one-directional dial.

Don’t expect JIT speedups either

A related, often-repeated misconception: that Garry’s Mod runs plain interpreted Lua and would benefit from “enabling JIT.” GMod actually runs on LuaJIT - but the JIT compiler itself has been disabled since roughly update #161, a widely repeated community account (attributed to Garry Newman) that we could not confirm against an official changelog or wiki page, so treat the specific update number as folklore rather than settled fact. The reason commonly given is specific to this engine: GMod’s Lua API surface is almost entirely calls into the Lua-C API - engine bindings like hook.Call and entity methods - which LuaJIT’s trace recorder cannot compile. The trace recorder still runs, fails to find compilable traces, and the attempt itself adds overhead for zero benefit. Pure interpreter mode is measurably faster for GMod’s actual workload than JIT-on would be, which is also why jit.off() fixes some reported slowdowns.

The practical upshot for anyone optimizing hot paths (Think, per-tick hooks): treat the runtime as an interpreter. Cache function and table lookups locally in hot loops, avoid allocating tables per frame, and avoid unnecessary metatable indirection. There’s no compiler tier coming to bail out an inefficient hot path.

The cost is in the boundary, not the language

Two independently sourced, quantified case studies make the same point from different directions, and together they’re the strongest evidence available in this ecosystem for how to think about hook cost.

Bar chart: widgets.PlayerTick() disabled gave +42% server performance, Garry Newman's optimization pass gave +23%

First: a Facepunch-tracked engine issue benchmarked the built-in widgets.PlayerTick() hook - which runs every tick, for every player, unconditionally - under a 128-player stress test (1 human, 127 bots, tick rate 20). It measured a mean of 27,231µs per frame attributable to that single hook: roughly 31.1% of total frame time. Disabling it produced a measured 42% improvement in overall server performance. That’s not a rounding error from one hook.

Second, and more surprising: Garry Newman’s own optimization pass on the game (using timedemo, vprof, and Very Sleepy) took frame rate from 127 to 157 FPS - a 23% improvement - through targeted native-code changes. Notably, an attempt to reimplement the hook system itself in C++ made things roughly 15 FPS slower, because of repeated Lua↔C pcall transition overhead. Read together with the JIT finding above, the pattern is consistent: crossing the Lua/engine boundary repeatedly is itself the expensive part, not raw Lua execution speed. The correct lever is batching work into fewer hook calls - one hook that loops over all players, rather than N separate per-purpose hook registrations each paying their own transition cost.

What this means for a sizing or performance decision

Related reading: Engine Limits Every Server Owner Should Know Before They Hit Them covers the entity and networking ceilings that interact with tick-rate and hardware decisions the same way.