Server Vs Workstation GC
6 min read- Server Vs Workstation GC
- TL;DR
- How it works
- Server GC
- Workstation GC
- Latency & modes (apply to both flavors)
- Container & hosting notes (very relevant today)
- Choosing the right one (simple rules)
- Tuning checklist for a trading/back-office service
- Pitfalls & gotchas (the “senior” bits)
- Answers you can give quickly
- Quick recall Q&A
Server Vs Workstation GC
TL;DR
Server GC and Workstation GC are the two .NET garbage collector flavors: Server GC runs one parallel GC thread per core with larger heap segments to maximize throughput on multi-core hosts (the ASP.NET Core default), while Workstation GC uses a single GC thread and smaller segments to keep pauses short for interactive desktop apps. Pick Server GC for APIs and services, but measure — tiny CPU-capped containers sometimes do better on Workstation GC.
How it works
| Aspect | Server GC | Workstation GC |
|---|---|---|
| Goal | Throughput for multi-core servers | Responsiveness for desktop/interactive apps |
| Threads | 1 GC thread per logical core (+ background GC) | 1 GC thread total (+ background GC) |
| Segment sizes | Larger heap segments → fewer GCs, longer pauses | Smaller segments → more frequent, shorter pauses |
| Compaction | Parallel, wide | Serial, narrower |
| Default in ASP.NET Core | Usually enabled | Not typical |
| Best for | Web APIs, services, batch jobs, high QPS | WPF/WinForms, tools, dev utilities |
Console.WriteLine(System.Runtime.GCSettings.IsServerGC); // true = Server GC
Server GC
- On startup, CLR creates one GC worker per core. During a blocking GC, all managed threads hit a safepoint; GC workers run in parallel to mark/compact.
- Background (concurrent) Gen2 collections run alongside the app. Gen0/Gen1 are still short stop-the-world.
- Bigger segments (ephemeral & Gen2) reduce GC frequency under high allocation rates (typical on APIs parsing JSON, serializing, buffering).
- Pauses can be longer, but total % time in GC is usually lower, boosting throughput.
Workstation GC
- Designed to feel snappy on a single user’s machine.
- Smaller segments → more frequent, shorter GCs.
- Background GC also exists, but there’s no parallel army of GC workers per core.
Latency & modes (apply to both flavors)
- Background GC (Gen2) is on by default; it overlaps some GC work with your app.
- Latency modes via
GCSettings.LatencyMode:
Interactive(balance; default for workstation).Batch(maximize throughput; longer pauses OK—common with server GC).SustainedLowLatency(reduce Gen2 frequency; good around critical windows).NoGCRegion(attempt zero collections during a critical section—must pre-reserve memory; avoid if you’ll hit LOH or exceed reservation).
Example:
using System.Runtime;
GCSettings.LatencyMode = GCLatencyMode.SustainedLowLatency;
Container & hosting notes (very relevant today)
- .NET is container-aware: CPU/memory limits influence GC heuristics.
- Environment toggles:
DOTNET_GCServer=1(or legacyCOMPlus_GCServer=1) → Server GC on.- Heap caps:
DOTNET_GCHeapHardLimitor%variant to avoid over-commit. - If you CPU-cap a container (e.g.,
--cpus=2), Server GC will create GC workers according to the limit, not host machine total (Core 3.0+). - Small microservices with very tight limits sometimes perform better with Workstation GC (fewer GC worker threads contending). Measure.
Choosing the right one (simple rules)
Use Server GC when:
- ASP.NET Core APIs / gRPC / background services on multi-core hosts.
- High allocation rates (serialization, buffers, streams).
- You prioritize throughput and can tolerate slightly longer pauses.
Use Workstation GC when:
- UI/desktop apps where interaction latency matters.
- Tiny sandboxed processes with strict CPU quotas (1 vCPU) where Server GC parallelism provides little benefit.
Tuning checklist for a trading/back-office service
- Ensure Server GC in production:
- Set
DOTNET_GCServer=1in env or project runtimeconfig. - Verify with
GCSettings.IsServerGC.
- Watch LOH churn (≥ 85 KB allocations): pool large buffers (
ArrayPool<T>), stream withSystem.IO.Pipelines, useUtf8JsonReader. - Latency windows (market open/rollover):
- Temporarily set
SustainedLowLatencyor carefully useNoGCRegion.
- Measure, don’t guess:
dotnet-counters monitor System.Runtime- Key signals: Gen2 GC Count (should be rare), % Time in GC (low single digits), Allocated Bytes/sec (track spikes).
- Avoid manual
GC.Collect()in steady state—only for controlled maintenance (e.g., one-off LOH compaction during off-peak).
Pitfalls & gotchas (the “senior” bits)
- Server GC isn’t always faster if your service is CPU-starved (1 core) or allocation-light. Validate with load tests.
- Background Gen2 is not free: if promotion pressure is high (many objects reaching Gen2), you’ll still see noticeable pauses. Reduce promotions by:
- Shortening object lifetimes (don’t cache what you don’t need).
- Pooling and reusing (buffers, parsers).
- Pinned memory (interop) prevents compaction; pin briefly or copy into a staging buffer to keep the SOH healthy.
- LOH fragmentation grows pause times over days/weeks. If you must allocate ≥85 KB often, restructure to chunk (e.g., 64 KB + 32 KB) or reuse pooled slabs.
Answers you can give quickly
GCSettings.IsServerGC at startup, and dotnet-counters/App Insights to watch Gen2 counts, % time in GC, and allocation rate. If Gen2 climbs or LOH grows, we pool buffers and reduce promotions.”If you want, I can give you a 30-second script you can say verbatim when they ask “Server vs Workstation GC—when and why?”
Quick recall Q&A
Set DOTNET_GCServer=1 (or configure in runtimeconfig) for Server GC. Without it, Workstation GC is used by default for desktop apps. Always verify with GCSettings.IsServerGC.
It creates a GC worker per core and uses larger segments, reducing collection frequency and keeping throughput high under heavy allocation workloads common in APIs.
In small, CPU-limited containers or interactive desktop apps where shorter individual pauses matter more than raw throughput. Always measure both modes.
Both support GCSettings.LatencyMode options (Interactive, Batch, SustainedLowLatency, NoGCRegion). Choose based on workload, not GC flavor.
.NET respects container CPU/memory limits when sizing GC segments and threads. If you limit CPUs, Server GC creates fewer worker threads accordingly.
Track % Time in GC, Gen2 counts, and LOH size via dotnet-counters or App Insights. Compare metrics when toggling between modes to justify the configuration.
No. GC mode is a process-wide setting configured at startup. You can’t run Server GC for some components and Workstation GC for others.
Pinning affects compaction regardless of GC mode. However, Server GC’s larger segments mean fragmentation can be more noticeable if you pin frequently.
Server GC runs background Gen2 collections in parallel. Workstation GC also supports background GC but with fewer worker threads, so concurrency benefits are smaller.
“Server GC maximizes throughput on multi-core servers via parallel collections; Workstation GC prioritizes responsiveness with shorter pauses. Choose based on workload and validate with GC metrics.”