How to Squeeze 8 Gbps of Video Traffic From a Single CPU Core in Go

Go is rarely the first language that comes to mind when you need to squeeze every last drop of performance out of a network server. Most developers reach for C, Rust, or even assembly when the goal is gigabit-rate packet processing. But a recent case study from the Russian engineering community, shared on Habr, demonstrates that Go can be pushed to an astonishing 8 Gbps of video traffic on a single CPU core. That's not a typo. The authors of the article document a series of optimization techniques that transform a typical Go network service into a high-throughput video delivery engine.

The news is significant not just for Go enthusiasts, but for anyone building edge servers, CDNs, or live-streaming infrastructure. In this post, we'll break down the techniques described in the source material, explain the engineering trade-offs, and add context from the broader performance-optimization ecosystem. Whether you're a seasoned systems programmer or a curious developer, you'll learn why Go's reputation as a "slow" language is increasingly outdated — and how you can replicate this kind of performance in your own projects.

The Challenge: Video Traffic Is Brutal

Video is the dominant form of internet traffic. According to Sandvine's continually updated reports, video streaming accounts for over 60% of downstream traffic globally. Yet video delivery is unforgiving: it requires high bandwidth, low and consistent latency, and minimal packet loss. For a server application, this means the network stack must move millions of packets per second without dropping frames.

The Habr article focuses on a real-world system that pushes video data to clients. The authors observed that after deploying on a standard server, they were struggling to exceed 1-2 Gbps per core. The CPU was spiking to 100%, yet throughput was mediocre. After a deep optimization sprint, they reached the headline 8 Gbps mark — a 4x improvement. The article covers the exact steps, and we'll summarize the key principles here.

Why Go's Default Networking Stack Is Not Enough

Go's standard library is designed for simplicity, not raw performance. The net package uses a per-connection goroutine model, which is great for concurrency but introduces overhead:

  • Each Read or Write call may allocate memory for buffers.
  • The runtime's network poller uses epoll under Linux, but each event still requires a goroutine wake-up.
  • Packet handling often copies data multiple times: kernel -> userspace buffer -> Go slice -> application logic -> socket.

For video traffic, which consists of large payloads (typically 1400-byte UDP packets or larger TCP segments), these costs multiply quickly. The result is that a naive Go server will sit at maybe 2-3 Gbps on a modern core, with the rest of the CPU burned on overhead.

The authors of the Habr article didn't rewrite everything in C. Instead, they implemented several targeted optimizations that kept the Go codebase but moved the bottleneck away from the runtime.

Key Technique #1: Zero-Copy Packet Processing

The most impactful change was eliminating data copies. In a traditional Go network loop, you call conn.Read(buf) and the kernel copies data from its internal socket buffer to the user-space buffer. Then, when you write to another socket, the data is copied again. For video relay, that's at least two copies.

The article describes using io_uring — Linux's async I/O interface — in combination with the github.com/iceber/iouring-go library. This allows the kernel to write directly to the destination socket without ever landing in a Go-managed slice. The data stays in kernel space, and the Go code only manages the request descriptors. This alone reduced CPU usage by nearly 50%.

If io_uring is unavailable (for example, on an older kernel), the authors also experimented with sendfile() for file-based video, but the streaming data came from upstream, so io_uring was the winning approach.

Key Technique #2: Batching and Reducing Syscalls

Every system call is a trap into the kernel, and while Go's netpoller helps, each packet still incurs at least one syscall. The fix is batching: process multiple packets in a single syscall.

The article covers two mechanisms:

  • recvmmsg / sendmmsg — batch receive/send on a UDP socket. The authors used this for the UDP-side input, allowing them to pull up to 64 datagrams per call.
  • io_uring's IORING_OP_RECV operation, which can be configured with a multishot mode to continuously post multiple receive operations without re-arming.

By batching, the code went from ~2 million syscalls per second down to ~30,000. That freed up serious CPU cycles.

Key Technique #3: Allocation Escape and sync.Pool

Garbage collection is the enemy of real-time traffic. A single GC pause can cause jitter, and the allocator itself consumes CPU. The article reports that after profiling with pprof, they discovered that a massive portion of CPU time was in mallocgc, not in actual network logic.

The solution was a multi-step approach:

  1. Reuse buffers using sync.Pool. Instead of allocating a fresh 1500-byte buffer per packet, the code checks out a buffer from the pool, processes the packet, and puts it back.
  2. Avoid pointer-heavy structures in the hot path. The authors redesigned the packet history map to use flat arrays with integer offsets, reducing pointer chasing.
  3. Set GOGC to a high level (e.g., GOGC=200 or turning off GC entirely with debug.SetGCPercent(-1) in short-lived high-throughput bursts). This trades memory for lower GC frequency.

These changes cut GC-related CPU from 35% to less than 5%.

Key Technique #4: Bypassing the Go Scheduler

Go's scheduler is cooperative and non-preemptive until recently (Go 1.14 introduced asynchronous preemption). But even with preemption, the scheduler must wake up goroutines for every I/O event. The article describes an interesting trick: they split the worker pool into dedicated OS threads using runtime.LockOSThread(), essentially running a fixed number of goroutines pinned to separate threads. Each goroutine then does blocking reads on its own socket, without the runtime's netpoller interfering.

This may sound like a step backwards, but it works when you have exactly one core to use. By pinning one goroutine to the core and setting the GOMAXPROCS equal to 1, they eliminated any chance of the scheduler migrating the goroutine or interleaving other work. The result is a simple busy-polling loop that reads from recvmmsg, processes the packets, and writes the output.

The code snippet from the article (simplified) looks like this:

runtime.LockOSThread()
defer runtime.UnlockOSThread()

for {
    n, _ := syscall.Recvmmsg(fd, msgs, 0)
    for i := 0; i < n; i++ {
        // process packet in place
        mh := &msgs[i]
        buf := mh.MsgHdr
        // ...
    }
}

There's no goroutine switch, no allocation, and no lock. That's the essence of the performance.

Key Technique #5: Profiling and Micro-Tuning

The authors didn't guess their way to 8 Gbps. They used Go's built-in profiling tools, plus a few external ones:

  • go tool pprof for CPU and heap profiles.
  • go tool trace to identify scheduler stalls.
  • perf for kernel-side analysis, which helped them spot cache misses and TLB pressure.

One crucial micro-tuning was aligning buffers to CPU cache lines (64 bytes). A simple alignToCacheLine function placed each packet buffer at an address divisible by 64. This reduced cache misses by 12%.

They also tuned the kernel's network parameters — increasing the receive queue size, enabling RPS (Receive Packet Steering) on the same core, and adjusting net.core.rmem_max. But their primary insight was that the userspace code was the bottleneck, not the kernel.

Real-World Relevance

What does this mean outside the experimental environment? The techniques in the article are directly applicable to:

  • Video relay servers that forward RTSP or HLS streams.
  • Game streaming proxies that need low-latency UDP relay.
  • Edge caches where a single server must handle many video sessions.
  • IoT gateways that aggregate sensor data over UDP.

Most production deployments of Go for video are probably still using the standard library. This article proves that a significant performance headroom exists, but reaching it requires a willingness to move away from idiomatic Go patterns and into lower-level syscall territory.

However, there is a trade-off. The code becomes less portable and more brittle. io_uring is Linux-only, and the dedicated-thread approach makes it harder to scale to multiple cores. The authors acknowledge that they optimized for the single-core case, which is a valid target for embedded or edge devices where you deliberately run one process per core.

The Bottom Line

The news that a single CPU core in Go can handle 8 Gbps of video traffic is both impressive and practical. It shows that Go's runtime is not a hard limit — it's just a default that you can bypass when things really matter. The authors shared their techniques openly, and the Go community has a lot to gain from adopting these patterns in high-throughput services.

We recommend reading the full Habr article for the specific code and benchmark setups: Source. While the article is in Russian, the diagrams and benchmark tables speak a universal language.

If you're building video infrastructure and want to see how such optimizations fit into a real-world product (including API-level integration with streaming protocols), the team at ASI Biont has been exploring similar performance envelopes. There's no need to walk the path alone — but when you do attempt zero-copy and batching, be prepared to leave the easy highway and drive your own cache lines.

In the end, the number 8 Gbps isn't a magic ceiling. It's just a data point that says: don't underestimate Go. The language is capable of more than you think, and with the right techniques, you can push it to the edges of what a single core can physically do.

← All posts

Comments