How to Benchmark EC2 C-Series CPU Performance?
Benchmarking single-threaded versus multi-threaded performance across Amazon EC2 C-series generations requires isolating processor architectures, standardizing operating system configurations, and executing synthetic and application-level workloads under controlled conditions. By testing instances such as C5, C6i, C6g, C7i, and C7g with tools like Sysbench, Stress-ng, and CoreMark, engineers can quantify IPC improvements, evaluate Simultaneous Multithreading scaling, and determine the optimal price-performance ratio for compute-intensive applications.
Understanding Amazon EC2 C-Series Architecture Differences
Amazon EC2 compute-optimized instances span multiple processor generations and instruction set architectures. Before running benchmarks, you must account for how each family maps virtual CPUs to physical cores, how turbo boost behaves under single-core versus all-core loads, and how cache hierarchies differ.
| Instance Family | Processor Architecture | Instruction Set | vCPU to Physical Core Mapping | All-Core Turbo Behavior |
|---|---|---|---|---|
| C5 / C5n | Intel Xeon Scalable (Skylake / Cascade Lake) | x86_64 | 2 vCPUs per physical core (SMT enabled) | Clock frequency drops under heavy all-core AVX-512 or multi-threaded load |
| C6i | Intel Xeon Scalable (Ice Lake) | x86_64 | 2 vCPUs per physical core (SMT enabled) | Higher sustained all-core turbo than C5 and improved per-core L2 cache |
| C6a | AMD EPYC (Milan) | x86_64 | 2 vCPUs per physical core (SMT enabled) | Consistent frequency scaling across CCX complexes |
| C6g | AWS Graviton2 (Neoverse N1) | ARM64 | 1 vCPU per physical core (No SMT) | Fixed clock frequency regardless of single-threaded or multi-threaded load |
| C7i | Intel Xeon Scalable (Sapphire Rapids) | x86_64 | 2 vCPUs per physical core (SMT enabled) | Dynamic frequency scaling with Advanced Matrix Extensions support |
| C7a | AMD EPYC (Genoa) | x86_64 | 1 vCPU per physical core (SMT disabled by default on EC2) | High linear multi-threaded scaling due to dedicated physical cores |
| C7g | AWS Graviton3 (Neoverse V1) | ARM64 | 1 vCPU per physical core (No SMT) | Static clock frequency with wider execution pipelines and DDR5 memory |
Understanding vCPU topology is critical when comparing single-threaded and multi-threaded metrics. On Intel-based C5, C6i, and C7i instances, a single vCPU is a hardware thread sharing execution resources with a sibling thread on the same physical core. On AWS Graviton (C6g, C7g) and AMD Genoa (C7a) instances, every vCPU corresponds to an independent physical core.
Standardizing the Benchmarking Environment
To produce repeatable results across generations and architectures, the software stack and kernel parameters must be identical across all test instances.
Operating System and Compiler Parity
Provision all instances with the same Linux distribution and kernel
version, such as Amazon Linux 2023 or Ubuntu 24.04 LTS. When compiling
benchmarks from source, use the same GCC or Clang version across both
x86_64 and aarch64 builds. Use
architecture-specific optimization flags
(-march=native -O3) so that each C-series generation can
utilize its supported vector instructions (such as AVX-512 on C6i/C7i
and SVE on C7g).
Instance Sizing and Noisy Neighbor Mitigation
Avoid benchmarking on large or xlarge
instance sizes when evaluating generational scaling, as smaller
instances share the underlying physical host and last-level cache with
other tenants. Use 8xlarge, 12xlarge,
16xlarge, or .metal sizes to minimize noisy
neighbor interference and observe NUMA topology effects.
CPU Governor and Topology Verification
Verify the CPU topology and ensure the operating system scaling
governor is set to performance where supported:
# Inspect physical cores, sockets, and threads per core
lscpu
# Set CPU frequency governor to performance if cpupower is available
sudo cpupower frequency-set -g performanceMeasuring Single-Threaded vs. Multi-Threaded Scaling
To analyze how an instance transitions from single-threaded execution to full multi-threaded saturation, run tests across two distinct thread counts: \(N = 1\) and \(N = \text{Total vCPUs}\). Additionally, on x86 instances with Simultaneous Multithreading, testing at \(N = \frac{\text{Total vCPUs}}{2}\) isolates physical core scaling before hyper-threading contention begins.
The theoretical speedup \(S(N)\) according to Amdahl's Law is defined as:
\[S(N) = \frac{1}{(1 - P) + \frac{P}{N}}\]
Where \(P\) is the parallelizable fraction of the workload and \(N\) is the number of threads. In real-world EC2 benchmarking, we measure the empirical Parallel Scaling Efficiency \(E(N)\) and the Multiprocessing Multiplier \(M\):
\[M = \frac{\text{Score}_{\text{Multi-Threaded}}}{\text{Score}_{\text{Single-Threaded}}}\]
\[E(N) = \frac{M}{N} \times 100\%\]
On Graviton instances (C6g, C7g) and C7a, where each vCPU is a physical core and clock frequencies remain flat under load, \(E(N)\) for compute-bound tasks typically approaches \(95\%\) to \(99\%\). On SMT-enabled instances (C5, C6i, C7i), running a single thread utilizes the full physical core and achieves peak single-core turbo frequency, whereas saturating all vCPUs splits physical core resources between sibling threads and reduces the clock multiplier, often resulting in \(E(N)\) values between \(55\%\) and \(70\%\).
Executing Synthetic CPU Benchmarks
Use a combination of integer, floating-point, cryptographic, and memory-latency benchmarks to capture a complete performance profile.
1. Sysbench CPU Benchmark
Sysbench calculates prime numbers up to a specified value, testing 64-bit integer division and branching performance.
# Install sysbench and numactl
sudo dnf install -y sysbench numactl
# Single-threaded execution pinned to physical core 0
numactl --physcpubind=0 sysbench cpu --cpu-max-prime=50000 --threads=1 --time=60 run
# Half-vCPU execution (Physical core count on x86 SMT instances)
sysbench cpu --cpu-max-prime=50000 --threads=$(($(nproc) / 2)) --time=60 run
# Full multi-threaded execution across all vCPUs
sysbench cpu --cpu-max-prime=50000 --threads=$(nproc) --time=60 runRecord the events per second metric and the
95th percentile latency from each run.
2. Stress-ng Matrix and CPU Methods
stress-ng provides hundreds of specific CPU stressor
methods (bogo ops/s) to test floating-point, vector, and
integer units independently.
# Install stress-ng
sudo dnf install -y stress-ng
# Single-threaded 64-bit floating-point matrix product test
taskset -c 0 stress-ng --matrix 1 --matrix-size 256 --timeout 60s --metrics-brief
# Multi-threaded 64-bit floating-point matrix product test across all vCPUs
stress-ng --matrix $(nproc) --matrix-size 256 --timeout 60s --metrics-brief3. OpenSSL Speed Benchmark
Cryptographic hashing and TLS throughput highlight generational instruction set enhancements (such as VAES on Ice Lake/Sapphire Rapids and crypto extensions on Graviton3).
# Single-threaded SHA256 and AES-256-GCM throughput
taskset -c 0 openssl speed -elapsed -evp aes-256-gcm
# Multi-threaded throughput across all available vCPUs
openssl speed -elapsed -multi $(nproc) -evp aes-256-gcmControlling Thread Affinity and Testing Without SMT
When comparing an 8-vCPU C7i (c7i.2xlarge, 4 physical
cores with 2 threads each) against an 8-vCPU C7g
(c7g.2xlarge, 8 physical cores with 1 thread each), you
must test how the x86 instance performs when threads are pinned strictly
to physical cores without sibling thread contention.
You can disable SMT dynamically inside the Linux guest OS to benchmark physical core parity:
# Disable SMT dynamically to test physical cores only
echo off | sudo tee /sys/devices/system/cpu/smt/control
# Verify active vCPU count
nproc
# Re-enable SMT after testing
echo on | sudo tee /sys/devices/system/cpu/smt/controlAlternatively, inspect which vCPUs share the same physical core so you can bind multi-threaded workloads cleanly:
# Display vCPU to Core mapping
lscpu -e=CPU,CORE,SOCKET,NODEOn EC2 Intel instances, the first half of the vCPU IDs (\(0\) to \(\frac{N}{2}-1\)) typically map to the primary threads on physical cores, while the second half (\(\frac{N}{2}\) to \(N-1\)) map to the sibling hyper-threads.
Automating Cross-Generation Sweeps
To eliminate cold-start variance and collect statistically
significant data across thread counts, use an automated bash harness
that steps through thread counts from \(1\) up to $(nproc):
#!/usr/bin/env bash
set -euo pipefail
INSTANCE_TYPE=$(curl -s http://169.254.169.254/latest/meta-data/instance-type || echo "unknown")
MAX_THREADS=$(nproc)
OUTPUT_FILE="benchmark_${INSTANCE_TYPE}.csv"
echo "instance_type,threads,events_per_sec,avg_latency_ms,p95_latency_ms" > "$OUTPUT_FILE"
for THREADS in 1 2 4 $((MAX_THREADS / 2)) $MAX_THREADS; do
# Deduplicate thread counts if instance has few vCPUs
if [ "$THREADS" -gt "$MAX_THREADS" ] || [ "$THREADS" -lt 1 ]; then
continue
fi
if [ "$THREADS" -eq 1 ]; then
CMD_PREFIX="taskset -c 0"
else
CMD_PREFIX=""
fi
RESULT=$($CMD_PREFIX sysbench cpu --cpu-max-prime=50000 --threads="$THREADS" --time=30 run)
EPS=$(echo "$RESULT" | awk '/events per second:/ {print $4}')
AVG_LAT=$(echo "$RESULT" | awk '/avg:/ {print $2}')
P95_LAT=$(echo "$RESULT" | awk '/95th percentile:/ {print $3}')
echo "${INSTANCE_TYPE},${THREADS},${EPS},${AVG_LAT},${P95_LAT}" >> "$OUTPUT_FILE"
doneCalculating Price-Performance Normalization
Raw performance numbers across C-series generations must be normalized against instance hourly on-demand or savings plan pricing to guide architectural decisions.
Compute two key metrics for each instance type:
\[\text{Single-Thread Cost Efficiency} = \frac{\text{Score}_{\text{1 Thread}}}{\text{Instance Hourly Price (\$)}}\]
\[\text{Multi-Thread Throughput per Dollar} = \frac{\text{Score}_{\text{All vCPUs}}}{\text{Instance Hourly Price (\$)}}\]
When evaluating workloads bound by single-thread latency—such as Redis, single-threaded Node.js processes, or sequential build steps—compare the single-thread throughput along with the per-vCPU hourly rate. When evaluating horizontally or vertically scalable workloads—such as video encoding, high-concurrency web servers, or batch analytics—use the Multi-Thread Throughput per Dollar metric at 80% to 100% CPU utilization to identify the most cost-effective C-series generation.