Sysbench 完全指南 / 第十章:内存测试
第十章:内存测试
10.1 概述
内存性能对系统整体性能有决定性影响,尤其是在数据库、大数据处理、科学计算等内存密集型场景。Sysbench 的 memory 测试模块可以测量内存带宽(Bandwidth)和延迟(Latency),帮助你评估系统内存子系统的性能。
10.1.1 测试原理
Sysbench 内存测试的原理:
- 分配指定大小的内存块(Block)
- 在内存块上执行连续的读或写操作
- 测量每秒完成的操作数和传输的数据量
- 支持不同的访问模式(顺序/随机)和操作类型(读/写/读写混合)
内存测试示意:
分配的内存块:
┌─────────────────────────────────────────────────┐
│ Block (如 1MB, 16MB, 256MB, 1GB) │
│ [ | | | | | | | | | | | | | | ] │
└─────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
顺序访问 → [0][1][2][3]... 或
随机访问 → [47][3][91][15]...
10.1.2 测试指标
| 指标 | 含义 | 单位 |
|---|---|---|
| MiB transferred | 传输的总数据量 | MiB |
| MiB/sec | 内存带宽 | MiB/s |
| total time | 总测试时间 | 秒 |
| avg latency | 平均延迟 | 毫秒 |
| 95th percentile | P95 延迟 | 毫秒 |
10.2 基本用法
10.2.1 最简单的内存测试
# 默认测试:单线程,写操作
sysbench memory run
# 指定线程数
sysbench memory --threads=4 run
10.2.2 输出解读
Total operations: 524288 (2621456.34 per second) ← 总操作数(每秒操作数)
512.00 MiB transferred (2560.02 MiB/sec) ← 传输数据量(带宽)
General statistics:
total time: 0.2000s
total number of events: 524288
Latency (ms):
min: 0.00
avg: 0.00
max: 0.12
95th percentile: 0.00
sum: 102.34
Threads fairness:
events (avg/stddev): 524288.0000/0.00
execution time (avg/stddev): 0.1023/0.00
10.3 内存测试选项
10.3.1 核心选项
| 选项 | 默认值 | 说明 |
|---|---|---|
--memory-block-size | 1K | 内存块大小 |
--memory-total-size | 100G | 总传输数据量 |
--memory-scope | global | 内存作用域:global(全局)/ local(每线程独立) |
--memory-hugetlb[=on/off] | off | 使用大页内存(HugePages) |
--memory-oper | write | 操作类型:read / write |
--memory-access-mode | seq | 访问模式:seq(顺序)/ rnd(随机) |
10.3.2 操作类型
# 写操作测试
sysbench memory --memory-oper=write --threads=4 --time=30 run
# 读操作测试
sysbench memory --memory-oper=read --threads=4 --time=30 run
10.3.3 访问模式
# 顺序访问(连续内存地址)
sysbench memory --memory-access-mode=seq --threads=4 --time=30 run
# 随机访问(随机内存地址)
sysbench memory --memory-access-mode=rnd --threads=4 --time=30 run
10.4 内存带宽测试
10.4.1 不同块大小的带宽测试
#!/bin/bash
# memory_bandwidth.sh - 内存带宽测试
echo "块大小,写带宽(MiB/s),读带宽(MiB/s)"
for bs in "1K" "4K" "16K" "64K" "256K" "1M" "4M" "16M" "64M" "256M" "1G"; do
# 写带宽
write_bw=$(sysbench memory \
--memory-block-size=$bs \
--memory-oper=write \
--memory-total-size=10G \
--memory-scope=global \
--threads=1 \
--time=30 run 2>&1 | grep "MiB/sec" | awk '{print $2}')
# 读带宽
read_bw=$(sysbench memory \
--memory-block-size=$bs \
--memory-oper=read \
--memory-total-size=10G \
--memory-scope=global \
--threads=1 \
--time=30 run 2>&1 | grep "MiB/sec" | awk '{print $2}')
echo "$bs,$write_bw,$read_bw"
done
预期结果:
块大小 写带宽 读带宽
1K ~2000 MiB/s ~3000 MiB/s ← 受 CPU 缓存影响
4K ~8000 MiB/s ~10000 MiB/s ← L1 缓存
16K ~15000 MiB/s ~18000 MiB/s ← L1/L2 缓存
64K ~20000 MiB/s ~25000 MiB/s ← L2 缓存
256M ~25000 MiB/s ~35000 MiB/s ← 超出缓存,测真实内存带宽
1G ~25000 MiB/s ~35000 MiB/s ← 真实内存带宽
关键:块大小应该大于 CPU 缓存(L3 Cache),才能测到真实的内存带宽。现代 CPU 的 L3 缓存通常为 8-64MB。
10.4.2 多线程带宽测试
#!/bin/bash
# memory_multi_thread_bw.sh - 多线程内存带宽
echo "线程数,写带宽(MiB/s),读带宽(MiB/s),写加速比,读加速比"
single_write=""
single_read=""
for threads in 1 2 4 8 16 32; do
write_bw=$(sysbench memory \
--memory-block-size=256M \
--memory-oper=write \
--memory-total-size=100G \
--memory-scope=global \
--threads=$threads \
--time=30 run 2>&1 | grep "MiB/sec" | awk '{print $2}')
read_bw=$(sysbench memory \
--memory-block-size=256M \
--memory-oper=read \
--memory-total-size=100G \
--memory-scope=global \
--threads=$threads \
--time=30 run 2>&1 | grep "MiB/sec" | awk '{print $2}')
if [ -z "$single_write" ]; then
single_write=$write_bw
single_read=$read_bw
w_speedup="1.00"
r_speedup="1.00"
else
w_speedup=$(echo "scale=2; $write_bw / $single_write" | bc)
r_speedup=$(echo "scale=2; $read_bw / $single_read" | bc)
fi
echo "$threads,$write_bw,$read_bw,$w_speedup,$r_speedup"
done
10.5 内存延迟测试
10.5.1 不同块大小的延迟测试
#!/bin/bash
# memory_latency.sh - 内存延迟测试
echo "块大小,平均延迟(ms),P95延迟(ms),最大延迟(ms)"
for bs in "1K" "4K" "16K" "64K" "256K" "1M" "4M" "16M" "64M" "256M" "1G"; do
result=$(sysbench memory \
--memory-block-size=$bs \
--memory-oper=read \
--memory-access-mode=rnd \
--memory-total-size=10G \
--memory-scope=global \
--threads=1 \
--time=30 run 2>&1)
avg=$(echo "$result" | grep "avg:" | awk '{print $2}')
p95=$(echo "$result" | grep "95th" | awk '{print $3}')
max=$(echo "$result" | grep "max:" | awk '{print $2}')
echo "$bs,$avg,$p95,$max"
done
延迟层次结构:
┌─────────────────────────────────────────────────────────────┐
│ 存储层次 容量 延迟 带宽 │
├─────────────────────────────────────────────────────────────┤
│ CPU 寄存器 < 1KB < 1ns 极高 │
│ L1 Cache 32-64KB 1-2ns > 100 GB/s │
│ L2 Cache 256KB-1MB 3-10ns 50-100 GB/s │
│ L3 Cache 8-64MB 10-30ns 20-50 GB/s │
│ 主内存 (DRAM) 16-512GB 50-100ns 20-50 GB/s │
│ NVMe SSD 1-8TB 10-100μs 3-7 GB/s │
│ SATA SSD 0.5-4TB 50-150μs 0.5-0.6 GB/s │
│ HDD 1-20TB 5-15ms 0.1-0.2 GB/s │
└─────────────────────────────────────────────────────────────┘
10.5.2 Pointer Chasing 延迟测试
更精确的延迟测试需要使用 Pointer Chasing 技术(Sysbench 不直接支持,需要自定义):
// pointer_chase.c - 简单的内存延迟测试
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <time.h>
#define ARRAY_SIZE (256 * 1024 * 1024 / sizeof(int)) // 256MB
int main() {
int *array = malloc(ARRAY_SIZE * sizeof(int));
// 初始化随机链表
for (int i = 0; i < ARRAY_SIZE; i++) array[i] = i;
// Fisher-Yates shuffle
for (int i = ARRAY_SIZE - 1; i > 0; i--) {
int j = rand() % (i + 1);
int tmp = array[i]; array[i] = array[j]; array[j] = tmp;
}
// Pointer chasing
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
int idx = 0;
for (int i = 0; i < 10000000; i++) {
idx = array[idx];
}
clock_gettime(CLOCK_MONOTONIC, &end);
double ns = (end.tv_sec - start.tv_sec) * 1e9 + (end.tv_nsec - start.tv_nsec);
printf("Average latency: %.1f ns\n", ns / 10000000);
printf("Dummy: %d\n", idx); // 防止优化
free(array);
return 0;
}
# 编译运行
gcc -O2 -o pointer_chase pointer_chase.c
./pointer_chase
# 典型结果:Average latency: 60-100 ns (DRAM)
10.6 HugePages 测试
10.6.1 大页内存简介
| 页面大小 | 使用场景 | 优势 |
|---|---|---|
| 4KB(默认) | 通用 | 灵活,操作系统默认 |
| 2MB(HugePages) | 数据库、JVM | 减少 TLB Miss |
| 1GB(GiganticPages) | 大型数据库 | 进一步减少 TLB Miss |
10.6.2 配置 HugePages
# 查看当前大页配置
cat /proc/meminfo | grep Huge
# 配置大页(假设需要 4GB 大页内存)
# 4GB / 2MB per page = 2048 pages
echo 2048 | sudo tee /proc/sys/vm/nr_hugepages
# 永久配置
echo "vm.nr_hugepages = 2048" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
# 验证
grep HugePages /proc/meminfo
10.6.3 HugePages 性能对比
#!/bin/bash
# hugepages_test.sh - HugePages 性能对比
echo "=== 普通页面 (4KB) ==="
sysbench memory \
--memory-block-size=256M \
--memory-oper=read \
--memory-total-size=100G \
--threads=8 \
--time=30 run 2>&1 | grep -E "(MiB/sec|avg:|95th)"
echo ""
echo "=== HugePages (2MB) ==="
sysbench memory \
--memory-block-size=256M \
--memory-oper=read \
--memory-total-size=100G \
--memory-hugetlb=on \
--threads=8 \
--time=30 run 2>&1 | grep -E "(MiB/sec|avg:|95th)"
注意:HugePages 的主要优势在大规模内存访问时才明显(减少 TLB Miss),小规模测试可能差异不大。
10.7 NUMA 架构测试
10.7.1 NUMA 对内存性能的影响
跨 NUMA 节点访问:
Node 0 Node 1
┌──────────┐ ┌──────────┐
│ CPU 0-15 │ │ CPU 16-31│
│ Memory │ │ Memory │
│ (local) │←─ QPI ──→│ (remote) │
└──────────┘ └──────────┘
本地访问:~80ns 跨节点访问:~130ns
10.7.2 NUMA 内存带宽测试
#!/bin/bash
# numa_memory_test.sh - NUMA 内存带宽测试
# 查看 NUMA 拓扑
echo "=== NUMA 拓扑 ==="
numactl --hardware
echo ""
# 在 Node 0 上运行(使用本地内存)
echo "=== Node 0 本地内存 ==="
numactl --cpunodebind=0 --membind=0 \
sysbench memory --memory-block-size=256M --memory-oper=read \
--memory-total-size=100G --threads=8 --time=30 run 2>&1 | \
grep "MiB/sec"
# 在 Node 0 上运行(使用 Node 1 的内存)
echo "=== Node 0 访问远程内存 ==="
numactl --cpunodebind=0 --membind=1 \
sysbench memory --memory-block-size=256M --memory-oper=read \
--memory-total-size=100G --threads=8 --time=30 run 2>&1 | \
grep "MiB/sec"
# 跨节点交织访问
echo "=== 交织内存访问 ==="
numactl --interleave=all \
sysbench memory --memory-block-size=256M --memory-oper=read \
--memory-total-size=100G --threads=16 --time=30 run 2>&1 | \
grep "MiB/sec"
10.8 内存测试场景
10.8.1 数据库 Buffer Pool 评估
#!/bin/bash
# db_buffer_pool_eval.sh
# 评估 Buffer Pool 大小对内存性能的影响
# 模拟不同 Buffer Pool 大小
for bs in "64M" "256M" "1G" "4G" "16G"; do
echo "Block Size (模拟 Buffer Pool): $bs"
# 顺序访问模式(类似 Buffer Pool 顺序扫描)
sysbench memory \
--memory-block-size=$bs \
--memory-oper=read \
--memory-access-mode=seq \
--memory-total-size=100G \
--threads=16 \
--time=30 run 2>&1 | grep "MiB/sec"
# 随机访问模式(类似 OLTP 随机查询)
sysbench memory \
--memory-block-size=$bs \
--memory-oper=read \
--memory-access-mode=rnd \
--memory-total-size=100G \
--threads=16 \
--time=30 run 2>&1 | grep "MiB/sec"
echo ""
done
10.8.2 内存压力测试
#!/bin/bash
# memory_stress.sh - 内存压力测试
TOTAL_MEM_GB=$(free -g | awk '/Mem/{print $2}')
TEST_SIZE="$((TOTAL_MEM_GB * 2))G" # 使用 2 倍物理内存
echo "=== 内存压力测试 ==="
echo "物理内存: ${TOTAL_MEM_GB}GB"
echo "测试数据量: $TEST_SIZE"
echo ""
# 使用所有核心
sysbench memory \
--memory-block-size=1M \
--memory-oper=write \
--memory-total-size=$TEST_SIZE \
--memory-scope=global \
--threads=$(nproc) \
--time=300 \
--histogram \
run
echo ""
echo "=== 监控内存使用 ==="
free -h
vmstat 1 5
10.8.3 内存带宽饱和测试
#!/bin/bash
# memory_saturation.sh - 测试内存带宽是否饱和
echo "线程数,读带宽(MiB/s),写带宽(MiB/s),总带宽(MiB/s)"
for threads in 1 2 4 6 8 10 12 14 16; do
read_bw=$(sysbench memory \
--memory-block-size=256M --memory-oper=read \
--memory-total-size=100G --threads=$threads --time=30 run 2>&1 | \
grep "MiB/sec" | awk '{print $2}')
write_bw=$(sysbench memory \
--memory-block-size=256M --memory-oper=write \
--memory-total-size=100G --threads=$threads --time=30 run 2>&1 | \
grep "MiB/sec" | awk '{print $2}')
total=$(echo "$read_bw + $write_bw" | bc)
echo "$threads,$read_bw,$write_bw,$total"
done
预期趋势:
线程数 读带宽 写带宽 说明
1 ~30000 ~20000 单通道
2 ~55000 ~38000 双通道
4 ~80000 ~55000 接近带宽上限
6 ~90000 ~62000 继续增长
8 ~95000 ~65000 接近饱和
10 ~97000 ~66000 轻微增长
12 ~98000 ~66500 基本饱和
14 ~98000 ~66500 饱和
16 ~98000 ~66500 饱和
10.9 典型内存带宽参考
10.9.1 DDR 内存规格
| 内存类型 | 频率 | 单通道带宽 | 双通道带宽 |
|---|---|---|---|
| DDR4-2133 | 2133 MHz | 17 GB/s | 34 GB/s |
| DDR4-2400 | 2400 MHz | 19.2 GB/s | 38.4 GB/s |
| DDR4-2666 | 2666 MHz | 21.3 GB/s | 42.6 GB/s |
| DDR4-3200 | 3200 MHz | 25.6 GB/s | 51.2 GB/s |
| DDR5-4800 | 4800 MHz | 38.4 GB/s | 76.8 GB/s |
| DDR5-5600 | 5600 MHz | 44.8 GB/s | 89.6 GB/s |
注意:Sysbench 的内存带宽测量值通常低于理论带宽,因为测试算法有额外开销。
10.9.2 云服务器内存性能参考
| 云服务商 | 实例类型 | 内存 | 读带宽 (MiB/s) | 写带宽 (MiB/s) |
|---|---|---|---|---|
| AWS | m6i.xlarge | 16GB | ~30000 | ~20000 |
| AWS | m6i.4xlarge | 64GB | ~60000 | ~40000 |
| 阿里云 | ecs.g7.xlarge | 16GB | ~28000 | ~19000 |
| 阿里云 | ecs.g7.4xlarge | 64GB | ~55000 | ~37000 |
注意:这些值仅为参考,实际结果受实例配置、物理机负载等因素影响。
10.10 注意事项
10.10.1 测试结果的影响因素
| 因素 | 影响 |
|---|---|
| CPU 缓存大小 | 小块测试受缓存影响,结果偏高 |
| 内存通道数 | 更多通道 = 更高带宽 |
| 内存频率 | DDR4-3200 > DDR4-2400 |
| NUMA 架构 | 跨节点访问延迟增加、带宽降低 |
| 其他进程 | 其他内存密集进程会影响结果 |
| BIOS 设置 | 内存频率、XMP 配置 |
10.10.2 常见误区
误区一:块大小 1K 测试结果就是真实内存带宽
- 小块测试实际上测的是 CPU 缓存性能
误区二:单线程就能测出最大带宽
- 现代 CPU 需要多线程才能饱和内存带宽
误区三:内存测试不需要预热
- 首次运行可能有 TLB 和缓存预热效应
误区四:忽略 NUMA 影响
- 在 NUMA 系统上,进程绑定到错误的 NUMA 节点会显著降低性能
10.11 小结
| 要点 | 说明 |
|---|---|
| 核心指标 | MiB/sec(带宽)和延迟 |
| 操作类型 | read / write |
| 访问模式 | seq(顺序)/ rnd(随机) |
| 块大小 | 测真实带宽需要大于 L3 缓存 |
| 多线程 | 多线程才能饱和内存带宽 |
| NUMA | 绑定到本地 NUMA 节点获取最佳性能 |
| HugePages | 大规模内存访问时有优势 |