結論直接看
整體而言,m6i.large(Intel Xeon)在 context switch 與高併發效能上顯著優於 m6a.large(AMD EPYC)。
m6i 更適合用於需要頻繁切換的工作負載,例如 Web API、短命週期容器、Gateway、或高併發微服務。
m6a 則雖在整體效能上略遜,但對長期運算型任務仍具價格優勢。
Wei 啥的結論
6 Instance Family 的 Intel 家族還是很可靠的,不用為了那一點點錢犧牲性能。
得不償失。
其實 7 Instance Family 我也測了一下。結論也是一樣,只是差距不像 6 Instance Family 這麼明顯。所以我收回 閉著眼睛選 m7a?m7i 在雲端容器時代怎麼輸的 中的評論,如果是 Web 或是 多工的場景,還是使用 Intel 系列吧。
起因
剛好遇到朋友問說,他任職公司最近正在打算節省 AWS 上面的費用,打算把所有的 6i 機型都換成 6a。(儘管 7a 的單核效能屌打 7i,但是 7a 比較貴。)
問我到底該不該這麼做。
而我剛好正在燒 Credit,也剛做完 m6i 與 m6a 的 CPU 性能評比。
首先先搞清楚需求,他們最常用來跑 Restful API,對於大流量上的需求非常明確。
那單純的跑分就沒什麼意義了,反而是在 CS 的比對下比較重要。
因為每次的 Request 都可以看成是多執行緒併發。
什麼是 Context Switch
Context Switch,中文稱作上下文切換或環境切換,是電腦術語,指CPU從執行一個行程(process)切換到另一個行程時,必須儲存前者當前的執行狀態(上下文),再載入後者的狀態,以便讓多個行程能共享單一CPU資源。 這個切換過程需要時間,稱為Context Switch的「成本」,也是實現多工(multitasking)的關鍵機制。
實驗
假設
那這樣設計實驗就很簡單了。
直接用 ab 狂打 nginx server 就可以了。
- 若 m6a 相比 m6i:
perf bench sched pipe 的平均尾端延遲更長
ab 場景下 cycles_per_switch 顯著更高(例如:1.3倍 到 2倍)
hackbench elapsed 更久
同時 p95/p99 延遲較差
則可合理歸因:切換排程每次成本較高,在高切換 I/O 場景體感「狀態卡頓」。
測試腳本
用 Linux 來跑,環境可以用在 Amazon Linux 2023 或是 ubuntu 22.04 上。
整體流程
- 建立輸出資料夾:依照時間建立
runs/YYYYMMDD_HHMMSS/。 - 偵測系統套件管理工具(
dnf,yum, 或apt-get)。 - 安裝測試所需套件:如
gcc,perf,sysstat,nginx,ab等。 - 蒐集系統資訊:CPU、記憶體、kernel 參數等。
- 建構測試程式
hackbench:自製的簡化 hackbench 負載測試。 - 準備 nginx:啟動 web server 給壓測工具用。
- 執行測試
lat_ctx:測量 thread context switch 延遲。perf bench sched pipe:內建的 pipe-based context switch 基準。hackbench:模擬多進程通訊的切換壓力。ab(ApacheBench)+perf stat:在實際 web 請求時測 cycles、context-switch、page-fault 等。perf stat(syscall mix):觀察系統呼叫分佈。 - 生成 SUMMARY.txt:整合所有結果(latency、每秒請求、每次切換週期等)。
#!/usr/bin/env bash
set -euo pipefail
# =========================
# Context Switch Repro Pack (v2)
# - Adds: --allowerasing for dnf
# - Adds: cycles_per_switch from perf stat during ab
# - Adds: perf bench sched pipe micro-benchmark
# - Makes lmbench/lat_ctx optional (build may fail on some distros)
# Tested on: Amazon Linux 2023 / AL2 / Ubuntu 22.04+
# =========================
TS="$(date +%Y%m%d_%H%M%S)"
OUT="runs/${TS}"
mkdir -p "${OUT}/logs" "${OUT}/bin" "${OUT}/src"
log() { echo "[$(date +%H:%M:%S)] $*"; }
detect_os() {
if command -v dnf >/dev/null 2>&1; then PM=dnf
elif command -v yum >/dev/null 2>&1; then PM=yum
elif command -v apt-get >/dev/null 2>&1; then PM=apt
else
echo "Unsupported OS: need dnf/yum/apt-get"; exit 1
fi
echo "${PM}" > "${OUT}/PM.txt"
}
install_deps() {
local pm="$1"
log "Installing dependencies via ${pm} ..."
case "${pm}" in
dnf)
sudo dnf -y update || true
sudo dnf -y install gcc gcc-c++ make git curl tar unzip \
sysstat perf nginx httpd-tools \
elfutils-libelf-devel zlib-devel --allowerasing
;;
yum)
sudo yum -y update || true
sudo yum -y install gcc gcc-c++ make git curl tar unzip \
sysstat perf nginx httpd-tools \
elfutils-libelf-devel zlib-devel
;;
apt)
sudo apt-get update -y || true
# linux-tools-$(uname -r) 可能抓不到,退而求其次安裝 linux-tools-common / generic
sudo apt-get install -y build-essential git curl tar unzip \
sysstat nginx apache2-utils linux-tools-common || true
sudo apt-get install -y "linux-tools-$(uname -r)" || true
sudo apt-get install -y linux-tools-generic || true
;;
esac
# enable sysstat (pidstat)
if command -v systemctl >/dev/null 2>&1; then
sudo systemctl enable --now sysstat >/dev/null 2>&1 || true
fi
}
build_lat_ctx() {
log "Building lmbench (lat_ctx optional) ..."
pushd "${OUT}/src" >/dev/null
if [ ! -d "lmbench" ]; then
git clone --depth=1 https://github.com/intel/lmbench.git || \
git clone --depth=1 https://github.com/ckolivas/lmbench.git lmbench || true
fi
if [ -d "lmbench/src" ]; then
cd lmbench/src
make results || true
make lat_ctx || true
if [ -f "lat_ctx" ]; then
cp -f lat_ctx "${PWD}/../../bin/"
else
echo "WARN: lat_ctx build failed; will skip lat_ctx test" | tee -a "${OUT}/SUMMARY.txt"
fi
else
echo "WARN: lmbench source not available; skip lat_ctx" | tee -a "${OUT}/SUMMARY.txt"
fi
popd >/dev/null || true
}
build_hackbench() {
log "Building hackbench-like ..."
pushd "${OUT}/src" >/dev/null
cat > hackbench.c <<'EOF'
#include <pthread.h>
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define MSG "hello"
int main(int argc, char **argv) {
int groups = 10, loops = 10000;
if (argc > 1) groups = atoi(argv[1]);
if (argc > 2) loops = atoi(argv[2]);
for (int g=0; g<groups; g++) {
int fds[2]; if (pipe(fds) != 0) { perror("pipe"); exit(1); }
pid_t p = fork();
if (p < 0) { perror("fork"); exit(1); }
if (p == 0) {
for (int i=0; i<loops; i++) if (write(fds[1], MSG, sizeof(MSG)) < 0) perror("write");
close(fds[0]); close(fds[1]); _exit(0);
} else {
char buf[64];
for (int i=0; i<loops; i++) if (read(fds[0], buf, sizeof(MSG)) < 0) perror("read");
close(fds[0]); close(fds[1]); waitpid(p, NULL, 0);
}
}
printf("hackbench-like: groups=%d loops=%d done.\n", groups, loops);
return 0;
}
EOF
gcc -O2 -pthread -o hackbench hackbench.c
cp -f hackbench "${PWD}/../bin/"
popd >/dev/null
}
prep_nginx() {
log "Preparing nginx ..."
if command -v systemctl >/dev/null 2>&1; then
sudo systemctl enable --now nginx
else
sudo nginx || true
fi
echo "hello $(hostname) @ $(date)" | sudo tee /usr/share/nginx/html/index.html >/dev/null
}
run_lat_ctx() {
if [ ! -x "${OUT}/bin/lat_ctx" ]; then
log "lat_ctx not available; skipping"
return
fi
log "Running lat_ctx ..."
for s in 64 128 256 512; do
"${OUT}/bin/lat_ctx" -s "${s}" 2 4 8 16 | tee -a "${OUT}/logs/lat_ctx_${s}.log"
done
}
run_hackbench() {
log "Running hackbench-like ..."
for G in 10 50; do
/usr/bin/time -f "elapsed_sec=%E cs_switched=%w" \
"${OUT}/bin/hackbench" "${G}" 20000 \
2>&1 | tee -a "${OUT}/logs/hackbench_g${G}.log"
done
}
run_perf_pipe() {
log "Running perf bench sched pipe ..."
perf bench sched pipe -l 100000 > "${OUT}/logs/perf_pipe.txt" 2>&1 || true
}
run_web_ab() {
log "Running nginx + ab with perf stat (cycles,cs) ..."
sleep 1
pgrep nginx || sudo nginx || true
local NGINX_PIDS
NGINX_PIDS="$(pgrep -d, nginx || true)"
echo "NGINX_PIDS=${NGINX_PIDS}" > "${OUT}/logs/nginx_pids.txt"
( pidstat -w -t 1 -p ALL > "${OUT}/logs/pidstat_web.txt" ) &
PIDSTAT_BG=$!
# warm-up
ab -n 20000 -c 200 http://127.0.0.1/index.html >/dev/null 2>&1 || true
# main run + perf (system-wide)
perf stat -a -e cycles,instructions,context-switches,cpu-migrations,task-clock,page-faults \
ab -n 200000 -c 500 http://127.0.0.1/index.html \
> "${OUT}/logs/ab_stdout.txt" 2> "${OUT}/logs/perf_ab.txt" || true
sleep 1
kill ${PIDSTAT_BG} >/dev/null 2>&1 || true
}
run_perf_syscall_mix() {
log "Measuring syscalls during hackbench ..."
if perf list 2>/dev/null | grep -q syscalls:sys_enter; then
perf stat -a -e context-switches,syscalls:sys_enter_read,syscalls:sys_enter_write,syscalls:sys_enter_futex \
"${OUT}/bin/hackbench" 30 30000 \
> "${OUT}/logs/perf_syscalls_out.txt" 2> "${OUT}/logs/perf_syscalls_stat.txt" || true
else
perf stat -a -e context-switches \
"${OUT}/bin/hackbench" 30 30000 \
> "${OUT}/logs/perf_syscalls_out.txt" 2> "${OUT}/logs/perf_syscalls_stat.txt" || true
fi
}
collect_system_info() {
log "Collecting system info ..."
{
echo "===== uname -a ====="; uname -a
echo; echo "===== CPU ====="; lscpu || cat /proc/cpuinfo
echo; echo "===== Memory ====="; free -h || true
echo; echo "===== Kernel cmdline ====="; cat /proc/cmdline || true
echo; echo "===== Sched tunables ====="
for f in /proc/sys/kernel/sched_*; do echo "$f=$(cat $f)"; done 2>/dev/null || true
echo; echo "===== Instance type (IMDS) ====="
curl -s --max-time 2 -H "Metadata-Flavor: Amazon" \
http://169.254.169.254/latest/meta-data/instance-type || true
echo
} > "${OUT}/SYSTEM.txt"
}
summarize() {
log "Summarizing ..."
{
echo "# Context Switch Repro Summary"
echo "Timestamp: ${TS}"
echo
echo "## Platform"
echo "Package Manager: $(cat ${OUT}/PM.txt)"
echo
echo "## Microbench"
if [ -f "${OUT}/logs/lat_ctx_64.log" ]; then
echo "- lat_ctx samples:"
grep -H "size=" -n ${OUT}/logs/lat_ctx_*.log || true
else
echo "- lat_ctx: not available (build failed or skipped)"
fi
echo
echo "- hackbench-like (elapsed/cs_switched if available):"
grep -H "elapsed_sec" -n ${OUT}/logs/hackbench_*.log || true
echo
echo "- perf bench sched pipe (pure cs latency):"
grep -E "avg|Percentiles" -n "${OUT}/logs/perf_pipe.txt" || true
echo
echo "## Web workload"
echo "- ab result:"
grep -E "Requests per second|Time per request|50%|90%|95%|99%" -n ${OUT}/logs/ab_stdout.txt || true
echo
echo "- perf (system-wide during ab):"
grep -E "cycles|instructions|context-switches|cpu-migrations|task-clock|page-faults" -n ${OUT}/logs/perf_ab.txt || true
echo
echo "- perf cycles/context-switches (during ab):"
awk '
/[0-9]+\s+cycles/ {c=$1}
/[0-9]+\s+context-switches/ {cs=$1}
END { if (c>0 && cs>0) printf(" cycles_per_switch ~= %.1f\n", c/cs); else print " (insufficient data)"; }
' "${OUT}/logs/perf_ab.txt" || true
echo
echo "## Syscall mix (hackbench)"
grep -E "context-switches|syscalls:sys_enter_" -n ${OUT}/logs/perf_syscalls_stat.txt || true
echo
echo "## Interpretation Tips"
} > "${OUT}/SUMMARY.txt"
log "Done. Results in: ${OUT}"
echo "Open ${OUT}/SUMMARY.txt"
}
main() {
detect_os
install_deps "$(cat ${OUT}/PM.txt)"
collect_system_info
build_lat_ctx || true
build_hackbench
prep_nginx
run_lat_ctx
run_perf_pipe
run_hackbench
run_web_ab
run_perf_syscall_mix
summarize
}
main "$@"
結果
m6a 為 AMD EPYC 9R14,m6i 為 Intel Xeon 8375C。
Hackbench
在 hackbench 測試中,m6i 明顯更快:
- g10 測試:m6a 約 0.18 秒,m6i 只需 0.07 秒。
- g50 測試:m6a 約 0.91 秒,m6i 只需 0.36 秒。
整體來看,m6i 約快 2.5 倍。這代表它在多群組 Processes 間通訊與 context switch 場景中反應更快。
Nginx + ab 壓力測試
以 Nginx 伺服器配合 ApacheBench 進行壓測(20 萬次請求、500 併發)。
- m6i 平均每秒可處理 22,201 requests/sec,
而 m6a 只有 11,862 requests/sec,效能幾乎高出 87%。 - 每次請求平均耗時:m6a 為 42.1 毫秒,m6i 下降至 22.5 毫秒。
- 延遲分位數部分:
- p50:41ms → 22ms
- p90:47ms → 25ms
- p95:48ms → 25ms
- p99:50ms → 26ms
整體分佈顯示,m6i 不僅平均速度更快,尾端延遲也更穩定。
perf 系統層觀察
從 perf stat 的結果來看:
- m6i 的 context-switches 約為 296k 次(16.2K/sec),比 m6a 的 285k 次(8.4K/sec)多,代表它能處理更多切換而不造成卡頓。
- m6i 的 task-clock 為 18.3 秒,而 m6a 為 33.9 秒,執行效率顯著更高。
- page fault 與 cpu migration 數據相近,但 m6i 的遷移次數稍高,顯示核心調度更積極。
Syscall Mix
hackbench 測試中,m6a 約有 376 次 context-switch,m6i 為 304 次。
雖切換次數略少,但整體耗時更短,顯示 m6i 的切換開銷更低。
結論
整體而言,m6i.large(Intel Xeon 8375C)在 context switch 與高併發效能上顯著優於 m6a.large(AMD EPYC 9R14)。
m6i 更適合用於需要頻繁切換的工作負載,例如 Web API、短命週期容器、Gateway、或高併發微服務。
m6a 則雖在整體效能上略遜,但對長期運算型任務仍具價格優勢。
若系統對互動延遲敏感、或經常出現「偶爾卡一下」的體感問題,m6i 會是更穩定的選擇。














