强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Sysbench 完全指南 / 第十章:内存测试

第十章:内存测试

10.1 概述

内存性能对系统整体性能有决定性影响,尤其是在数据库、大数据处理、科学计算等内存密集型场景。Sysbench 的 memory 测试模块可以测量内存带宽(Bandwidth)和延迟(Latency),帮助你评估系统内存子系统的性能。

10.1.1 测试原理

Sysbench 内存测试的原理:

  1. 分配指定大小的内存块(Block)
  2. 在内存块上执行连续的读或写操作
  3. 测量每秒完成的操作数和传输的数据量
  4. 支持不同的访问模式(顺序/随机)和操作类型(读/写/读写混合)
内存测试示意:

分配的内存块:
┌─────────────────────────────────────────────────┐
│ 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 percentileP95 延迟毫秒

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-size1K内存块大小
--memory-total-size100G总传输数据量
--memory-scopeglobal内存作用域:global(全局)/ local(每线程独立)
--memory-hugetlb[=on/off]off使用大页内存(HugePages)
--memory-operwrite操作类型:read / write
--memory-access-modeseq访问模式: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-21332133 MHz17 GB/s34 GB/s
DDR4-24002400 MHz19.2 GB/s38.4 GB/s
DDR4-26662666 MHz21.3 GB/s42.6 GB/s
DDR4-32003200 MHz25.6 GB/s51.2 GB/s
DDR5-48004800 MHz38.4 GB/s76.8 GB/s
DDR5-56005600 MHz44.8 GB/s89.6 GB/s

注意:Sysbench 的内存带宽测量值通常低于理论带宽,因为测试算法有额外开销。

10.9.2 云服务器内存性能参考

云服务商实例类型内存读带宽 (MiB/s)写带宽 (MiB/s)
AWSm6i.xlarge16GB~30000~20000
AWSm6i.4xlarge64GB~60000~40000
阿里云ecs.g7.xlarge16GB~28000~19000
阿里云ecs.g7.4xlarge64GB~55000~37000

注意:这些值仅为参考,实际结果受实例配置、物理机负载等因素影响。


10.10 注意事项

10.10.1 测试结果的影响因素

因素影响
CPU 缓存大小小块测试受缓存影响,结果偏高
内存通道数更多通道 = 更高带宽
内存频率DDR4-3200 > DDR4-2400
NUMA 架构跨节点访问延迟增加、带宽降低
其他进程其他内存密集进程会影响结果
BIOS 设置内存频率、XMP 配置

10.10.2 常见误区

  1. 误区一:块大小 1K 测试结果就是真实内存带宽

    • 小块测试实际上测的是 CPU 缓存性能
  2. 误区二:单线程就能测出最大带宽

    • 现代 CPU 需要多线程才能饱和内存带宽
  3. 误区三:内存测试不需要预热

    • 首次运行可能有 TLB 和缓存预热效应
  4. 误区四:忽略 NUMA 影响

    • 在 NUMA 系统上,进程绑定到错误的 NUMA 节点会显著降低性能

10.11 小结

要点说明
核心指标MiB/sec(带宽)和延迟
操作类型read / write
访问模式seq(顺序)/ rnd(随机)
块大小测真实带宽需要大于 L3 缓存
多线程多线程才能饱和内存带宽
NUMA绑定到本地 NUMA 节点获取最佳性能
HugePages大规模内存访问时有优势

扩展阅读