The empire strikes back — C++ enters the fray. C++ vs Rust vs C# vs Go

Part — 2 of the performance comparison in Kubernetes

B Shyam Sundar
5 min readSep 3, 2023
Language Performance

Introduction

This is part-2 of the performance comparison series. Read that before reading this. I have made some changes to the benchmarking process based on readers feedback.

Disclaimer

Please note that I am no expert in the C++. I am just interested in this understanding how each language performs under stress. Henc, if you think you can improve the performance of the C++ program, you are most welcome to fork my repo and improve upon it.

Prerequisites

This article assumes that you have a basic understanding of C++, Rust, C#, Go, Docker, Terraform, Postgres and Kubernetes.

Changes from Part-1

Based on feedback from you awesome readers, I have made the following changes to the code base.

  1. Added C++ — Drogon
  2. Bumped Go to 1.20 from 1.19 and slightly updated the fibonacci generation code
  3. Changed the Go’s framework from Gin to Atreugo. I have set prefork to true. For more info on PreFork, read the atreugo documentation here.
  4. Bumped Rust to 1.71 from 1.65
  5. Removed swagger endpoint from the C# project
  6. Changed the Fibonacci input number from a constant (40) to a random number between 5 to 40

Link to the source code.

Performance monitoring

Configuration

Every container will have the following configuration:

Requests: memory of 512 Mi and cpu of 500m

Limits: memory of 1024 Mi and cpu of 1000m

Initial Observation

Without any load the base running application benchmarks are showcased below.

CPU% — No Load

CPU — No Load

Memory Usage — No Load

The yellow line represents C++, red line represents Rust, Blue line represents Go and the Green line represents C#.

In idle state rust’s CPU usage is slightly higher than Go and Go is slightly higher than C# and C++. C#’s memory usage when idle is slightly higher than Go, and Go’s memory usage is slightly higher than C++. Rust’s memory usage is very minimal.

Load for GET Request

We are going to use an excellent tool called bombardier.

First we will hit all 4 services with 1 million requests from 50 connections and limit the rate to with 100 request per second.

Command for doing so is:

bombardier -n 1000000 -m GET -c 50 -r 100 <URL>

CPU% — Under Load

C# has the highest CPU usage of the lot averaging around 5%. Go, Rust and C++ has minimal usage with C++ CPU usage being the least.

Memory Usage — Under Load

C# memory usage is certainly higher in this scenario, followed by Go. But C++ and Rust’s memory usage hasn’t changed.

Let us look at latencies.

p99 — Under Load

p99 (99% of requests being served):

C++ — 4.97 ms C# — 4.96 ms, Go — 4.96 ms, Rust — 4.95 ms

Rust has a slight edge over others.

Bombardier stats:

C++:

Rust:

C#:

Go:

C# seems to be have the highest throughput.

Load for POST Request with limits

Now let us hit all 3 services with 1 million requests from 50 connections and limit the rate to with 100 request per second. But this time we will use a single connection making 1 request every second.

Bombardier command is

bombardier -n 1000000 -m POST -c 1 -r 1 <URL>
CPU% — POST Load

C++, Go and C# seem to fluctuate in CPU usage.

Memory usage — POST load

C# has the highest memory usage.

p99 — POST Load

Latency is where things get interesting. Go’s latency is trying to keep up with C# it isn’t consistent. Rust seems to be shining as it seems have half the latency of C#.

Bombardier stats:

C++:

Rust:

C#:

Go:

C# seems to have the highest throughput.

Conclusion

From this benchmark, I am able to understand that my implementation of C++ is not efficient and Rust has consistent performance and is almost always faster than C# and Go while C++ seems to be a slightly fluctuating which I would mostly allude to the way how I have implemented the code. While C++ is an amazingly powerful tool, it goes to show that it requires a level of mastery to really wield that power.

--

--