CCD and Cache Locality: Why Two Threads on the Wrong Cores Cost 15% TPS

On modern AMD and Intel server CPUs, core numbering is not flat. AMD EPYC and Ryzen parts split into Core Complex Dies (CCDs), each with its own L3 cache slice and memory path; Intel split-core designs do something similar with their ring and tile topology. A game server process that spreads its threads across two CCDs pays an inter-CCD latency penalty on every shared-memory access. On a Minecraft or Valheim node, that penalty shows up directly as lost TPS. This article shows how to detect it and fix it with thread pinning.

Why Cache Locality Matters for Game Servers

A game simulation loop touches hot data structures thousands of times per second: entity maps, chunk arrays, player state. When those structures fit inside one L3 slice (typically 32 MB per CCD on recent EPYC), the loop runs from cache. When two threads owning the same data structure run on different CCDs, every cross-thread access traverses the Infinity Fabric or mesh at 40-80 ns instead of 10-15 ns. Multiplied across a 20-tick loop with thousands of entity updates, the effect is a measurable drop in achievable tick rate.

Detecting Cross-CCD Placement

Linux exposes the cache topology through sysfs. Run these commands on your node to map which cores share an L3 slice:

  • lscpu -e=CPU,CORE,SOCKET,NODE,CACHE shows logical layout and NUMA nodes.
  • cat /sys/devices/system/cpu/cpu0/cache/index3/shared_cpu_list lists cores sharing that L3 slice.
  • lstopo --of txt (from hwloc) prints a readable tree of packages, CCDs, and caches.
  • numastat -p <pid> reveals NUMA misses for a running server process.

If the JVM or game binary shows a thread group split across two different shared_cpu_list ranges, you have a locality problem.

The Fix: Pin the Sim Thread to One CCD

The primary simulation thread and its GC or allocator threads should live on cores inside a single CCD. On a typical EPYC 8-core-per-CCD layout, that means pinning to cores 0-7 rather than letting the scheduler roam across 0-15. For a single instance, restrict the process to one CCD:

  • taskset -c 0-7 java -jar server.jar binds the process to the first CCD.
  • In systemd, set CPUAffinity=0-7 in the unit file.
  • For containers, use cpuset.cpus=0-7 in the cgroup.
  • Match NUMA memory with numactl --cpunodebind=0 --membind=0.

Multi-Instance Placement Strategy

When you host several game instances on one machine, assign each instance its own CCD rather than interleaving them. Two Minecraft worlds each pinned to separate 8-core CCDs will outperform the same two worlds spread randomly across 16 cores, because neither instance evicts the other’s hot working set. This is the practical argument for owning a machine with many CCDs when you run a multi-game community.

ScenarioPlacementRelative TPS
Single instance, one CCDcores 0-7100%
Single instance, two CCDscores 0-1585-90%
Two instances, separate CCDs0-7 and 8-15100% / 100%
Two instances, interleavedboth on 0-1578-85% each

Measuring the Improvement

Record TPS and MSPT (milliseconds per tick) before and after pinning. A well-behaved server should hold 20 TPS with MSPT under 30 ms. If pinning to one CCD pushes MSPT from 45 ms to 34 ms, that improvement is the inter-CCD tax you were paying. Use /forge tps, /spark tps, or your server’s native timing command, and run each configuration for at least 30 minutes under representative player load.

Interactions With Other Tuning

  • Disable transparent hugepage migration across NUMA nodes if it causes periodic stalls (echo 0 > /sys/kernel/mm/transparent_hugepage/khugepaged/defrag).
  • Set CPU governor to performance so pinned cores never idle down.
  • Keep GC threads on the same CCD as the heap owner to avoid cross-die allocation traffic.
  • Verify IRQ affinity: pin NIC interrupts away from the sim cores.

Bottom Line

Cache topology is invisible in a spec sheet but decisive in a tick loop. Before you buy more cores, check whether your current cores are sharing the right cache. Our bare-metal server lineup exposes full core and cache topology so you can pin with confidence, and the CPU pinning and NUMA guide covers the memory-binding half of the problem.

Leave a Reply