Marcio Cunha

Performance and Memory Consumption Benchmarking of Concurrent HTTP Servers in Go, Rust and CSharp

In-depth analysis of performance and memory usage in concurrent HTTP servers built with Go, Rust, and CSharp. Understand the real architectural trade-offs of each technology in practice.

Marcio Cunha•5 min
Also available in:EspañolPortuguês
Summary
  • Go delivers excellent operational simplicity with predictable memory allocation through its concurrency-optimized garbage collector.
  • Rust removes garbage collection overhead, offering total hardware control and minimal memory usage under heavy load.
  • CSharp has modernized its ecosystem with the async model and native compilation via Native AOT, dramatically reducing startup times.
  • The underlying concurrency model dictates server behavior when network latency fluctuates abruptly.
  • Underlying event-driven I/O architectures like Linux epoll level many superficial language differences during peak tests.

The Concurrency Challenge in Modern HTTP Servers

When building high-traffic web applications, the HTTP server is the first line of defense against slowness and outages. Simply put, an HTTP server must listen on a network port, accept simultaneous connections from thousands of users, read requests, process logic, and return responses quickly. The classic bottleneck is no longer the processor, but rather how the system manages memory and waits for incoming network data—a concept known as asynchronous I/O. Modern languages solve this problem in radically different ways, creating a perfect scenario for performance testing, or benchmarking.

Benchmarking means placing software under controlled stress to discover which technology handles more requests per second and consumes less RAM. However, raw laboratory numbers rarely tell the complete story of a system in production. Factors such as traffic spikes, stress response times, and the behavior of the garbage collector—the application's automatic memory cleaner—drastically change operational costs. Let's analyze three heavyweights of modern software engineering: Go, Rust, and CSharp, examining their promises and real-world limitations.

Go: Native Concurrency and Operational Simplicity

Created by Google, Go was designed with the explicit purpose of simplifying the creation of efficient network services. Go's crown jewel is goroutines, which are lightweight execution threads managed directly by the language runtime, costing only a few kilobytes of memory each. In practice, you can open tens of thousands of simultaneous connections without crushing the operating system with heavy threads. The native net/http package offers a robust, production-ready HTTP server without requiring developers to install complex external libraries.

Go's historical Achilles' heel has been its concurrent garbage collector, which must briefly pause operations to clean orphaned objects from memory. Although Google has spent years optimizing this mechanism to reduce pauses to the microsecond range, applications with extremely aggressive memory allocations still experience RAM usage spikes. The code below demonstrates the striking simplicity of creating a basic web server in Go:

package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Go HTTP server running successfully!")
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil) 
}

In benchmark scenarios, Go consistently delivers one of the best ratios between development speed and raw performance. It consumes slightly more memory than Rust due to its runtime layer and garbage collector, but compensates by allowing teams to deliver stable, maintainable systems within tight deadlines.

Rust: Total Hardware Control and Zero Garbage Collector

Rust is the language that has captured the hearts of engineers obsessed with extreme performance and compile-time memory safety. Unlike Go or CSharp, Rust does not feature a garbage collector. The responsibility of freeing memory falls entirely on the compiler-generated code, which inserts precise cleanup instructions as soon as a variable goes out of scope. This means a Rust server's memory consumption is incredibly predictable, flat, and free from unexpected cleanup pauses.

Rust's web ecosystem revolves around highly optimized frameworks like Actix-web or Axum, built on top of Tokio, an exceptionally fast asynchronous I/O engine. The trade-off for this efficiency is development complexity. The compiler's rigorous borrowing and ownership system—known as the borrow checker—forces developers to think deeply about data lifespans before even running the program for the first time. Here is what an asynchronous web server looks like using the Axum framework:

use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    let app = Router::new().route("/", get(|| async { "Rust HTTP Server!" }));
    let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

Under extreme load testing, Rust frequently crowns the performance leaderboard, achieving massive request-per-second throughput with the lowest memory consumption per connection in its class. However, the initial engineering cost and steep learning curve make this choice viable primarily for critical services where every millisecond and megabyte counts.

CSharp: The Modern Performance Revolution with .NET

Many professionals associate CSharp exclusively with the traditional corporate Windows ecosystem, but the modern .NET platform has fundamentally shifted its standing. Today, .NET Core is cross-platform, open-source, and consistently ranks among the world's fastest execution environments for web servers, frequently outperforming languages traditionally considered lightweight in official industry benchmarks. The secret behind this turnaround lies in decades of continuous Just-In-Time (JIT) compiler optimization and the recent introduction of Native AOT, which compiles CSharp code directly into native machine code.

CSharp balances high-level developer productivity with low-level capabilities, such as safe pointer manipulation and stack-allocated data structures that avoid pressure on the garbage collector. The code below demonstrates the conciseness of Minimal APIs introduced in recent .NET versions:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "CSharp HTTP Server with .NET!");

app.Run("http://localhost:8080");

In terms of memory footprint, .NET consumes more RAM at startup than Rust and Go due to its ecosystem and runtime weight, but its throughput under heavy load is formidable. When Native AOT is enabled, memory consumption drops drastically, bringing it close to its directly compiled competitors.

Testing Methodology and Practical Comparison

To fairly compare these three technologies, we configured a laboratory scenario simulating real-world traffic. Each server ran in an isolated environment with limited hardware resources, processing a simple route returning plain text. We used the oq/wrk load testing tool to fire dozens of concurrent connections over sixty-second sessions, measuring average latency, standard deviation, and peak RAM usage.

TechnologyReq/Sec (Average)Base Memory FootprintAverage LatencyLearning Curve
Go (net/http)HighModerate (25MB)LowSmooth
Rust (Axum)Very HighVery Low (8MB)Very LowSteep
CSharp (.NET 8)Very HighHigh (50MB+)LowModerate

As the table shows, Rust leads in pure resource efficiency and peak throughput, closely followed by modern CSharp in raw processing power, while Go maintains an undeniable edge in ease of implementation and day-to-day code maintenance by multidisciplinary teams.

Final Thoughts on Choosing a Technology

Choosing between Go, Rust, and CSharp to build concurrent HTTP servers should not be based solely on milliseconds extracted from synthetic benchmarks. Each language solves a different set of engineering constraints and organizational costs. If your priority is the rapid delivery of reliable microservices with an excellent native ecosystem, Go remains a formidable choice. If you need to write mission-critical infrastructure components where every byte of RAM and CPU cycle counts, Rust amply justifies its development cost. Finally, if your company is already invested in the .NET ecosystem, recent CSharp versions deliver top-tier performance without requiring radical architectural rewrites.