内存性能分析与优化

内存是系统性能的关键因素,理解内存管理机制对优化至关重要。

内存性能基础

内存层次结构

存储金字塔

速度快、容量小、成本高
    ↓
CPU 寄存器:< 1ns,几十个
    ↓
L1 缓存:~1ns,64KB
    ↓
L2 缓存:~3ns,256KB
    ↓
L3 缓存:~10ns,8-64MB
    ↓
主内存:~100ns,GB 级
    ↓
SSD:~0.1ms,TB 级
    ↓
HDD:~10ms,TB 级
速度慢、容量大、成本低

Linux 内存管理

虚拟内存机制

  • 每个进程有独立的虚拟地址空间
  • 虚拟地址通过页表映射到物理地址
  • 支持内存超分配
  • 提供内存保护和隔离

页面大小

  • 标准页:4KB(x86-64)
  • 大页(Huge Pages):2MB 或 1GB
  • 大页减少 TLB 未命中,提升性能

内存区域

用户空间(低地址):
  ├─ 代码段(.text):只读,可共享
  ├─ 数据段(.data/.bss):已初始化/未初始化全局变量
  ├─ 堆(Heap):动态分配,向上增长
  ├─ 内存映射区:共享库、mmap 文件
  └─ 栈(Stack):局部变量,向下增长

内核空间(高地址):
  ├─ 内核代码和数据
  ├─ 页缓存(Page Cache)
  └─ Slab 缓存

内存性能指标

内存使用统计

总内存 vs 可用内存

MemTotal:物理内存总量
MemFree:完全未使用的内存
MemAvailable:可用于新进程的内存(包含可回收的缓存)

可用内存 = MemFree + Buffers + Cached - 不可回收部分

Buffers vs Cached

  • Buffers:块设备的元数据缓存(inode、目录等)
  • Cached:文件内容的页缓存
  • 两者都是可回收的,不是"被占用"

Swap 使用

  • SwapTotal:交换空间总量
  • SwapFree:空闲交换空间
  • SwapCached:已换入但仍在 Swap 的页面

内存压力指标

页面扫描速率

  • pgscan:扫描的页面数
  • 高扫描速率表示内存压力大

页面回收

  • pgsteal:回收的页面数
  • 直接回收(同步)vs 后台回收(异步)

Swap 活动

  • si:Swap In(换入)
  • so:Swap Out(换出)
  • 频繁 Swap 严重影响性能

OOM 事件

  • 内存耗尽触发 OOM Killer
  • 杀死占用内存最多的进程
  • 最后的防御手段

内存瓶颈识别

内存不足症状

明显症状

  • 大量 Swap 使用
  • 频繁的 Swap In/Out
  • OOM Killer 被触发
  • 进程分配内存失败

隐性症状

  • 页缓存命中率下降
  • 磁盘 I/O 增加(缓存不足)
  • 系统响应变慢
  • kswapd 进程 CPU 使用率高

内存泄漏特征

内存泄漏定义

  • 程序分配内存后不释放
  • 内存使用持续增长
  • 最终导致 OOM

识别方法

1. 监控进程内存趋势
   ├─ RSS(常驻内存)持续增长
   └─ 没有合理的业务解释

2. 检查内存分配
   ├─ 堆内存大小
   └─ 内存映射区域

3. 使用专用工具
   ├─ valgrind --leak-check=full
   ├─ AddressSanitizer(ASAN)
   └─ pmap 查看内存映射

Swap 抖动

什么是抖动(Thrashing)

  • 频繁的页面换入换出
  • 大部分时间用于 Swap I/O
  • 系统几乎无法工作

原因

  • 工作集(Working Set)大于物理内存
  • 多个进程竞争内存
  • Swap 空间配置不当

解决方案

  • 增加物理内存
  • 减少内存使用(优化代码)
  • 限制进程数量
  • 调整 swappiness 参数

内存性能优化

页缓存优化

页缓存的作用

  • 缓存文件内容
  • 减少磁盘 I/O
  • 提高文件访问性能

预读(Readahead)

原理:
  ├─ 检测顺序读取模式
  ├─ 提前读取后续数据
  └─ 减少等待时间

调整:
  blockdev --setra 4096 /dev/sda  # 设置预读大小(扇区)

页缓存刷新

手动刷新:
  sync                           # 同步所有脏页
  echo 1 > /proc/sys/vm/drop_caches  # 释放页缓存
  echo 2 > /proc/sys/vm/drop_caches  # 释放 dentry 和 inode
  echo 3 > /proc/sys/vm/drop_caches  # 释放所有缓存

自动刷新:
  vm.dirty_ratio = 20            # 脏页达到 20% 时同步刷新
  vm.dirty_background_ratio = 10 # 脏页达到 10% 时后台刷新

Huge Pages(大页)

标准页 vs 大页

标准页(4KB):
  ├─ TLB 覆盖范围:512 × 4KB = 2MB
  ├─ 页表层次多
  └─ 适合小内存访问

大页(2MB):
  ├─ TLB 覆盖范围:512 × 2MB = 1GB
  ├─ 减少 TLB 未命中
  └─ 适合大内存应用

Huge Pages 优势

  • 减少页表层次(3 级 → 2 级)
  • 提高 TLB 命中率
  • 减少页表管理开销
  • 降低内存碎片

配置 Huge Pages

查看配置:
  cat /proc/meminfo | grep Huge

预留大页:
  echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

应用使用:
  ├─ 显式:mmap() + MAP_HUGETLB
  └─ 透明:启用 THP(Transparent Huge Pages)

THP(透明大页)

优点:
  ├─ 自动管理
  ├─ 应用无需修改
  └─ 动态分配

缺点:
  ├─ 可能增加延迟(碎片整理)
  ├─ 内存使用增加
  └─ 某些场景性能下降

配置:
  echo always > /sys/kernel/mm/transparent_hugepage/enabled
  echo madvise > /sys/kernel/mm/transparent_hugepage/enabled  # 推荐
  echo never > /sys/kernel/mm/transparent_hugepage/enabled

NUMA 内存优化

NUMA 内存分配

策略:
  1. 本地分配(default)
     └─ 在访问 CPU 的本地节点分配

  2. 绑定分配(bind)
     └─ 指定节点分配

  3. 交叉分配(interleave)
     └─ 轮流在各节点分配

  4. 优先分配(preferred)
     └─ 优先某节点,满了再其他

NUMA 平衡

内核自动平衡:
  ├─ 检测远程内存访问
  ├─ 迁移页面到本地
  └─ 可能引入开销

禁用自动平衡:
  echo 0 > /proc/sys/kernel/numa_balancing

Swap 配置优化

swappiness 参数

范围:0-100
  ├─ 0:最大限度避免 Swap(实际可能仍会 Swap)
  ├─ 60:默认值,平衡
  └─ 100:积极使用 Swap

调整:
  sysctl vm.swappiness=10
  echo 10 > /proc/sys/vm/swappiness

Swap 空间大小

传统建议:
  内存 < 2GB:Swap = 2 × 内存
  内存 > 2GB:Swap = 内存

现代建议:
  ├─ 服务器:较小 Swap(2-4GB)或无 Swap
  ├─ 桌面:与内存相当(支持休眠)
  └─ 云环境:通常不配置 Swap

zswap(压缩 Swap)

原理:
  ├─ Swap 前先压缩
  ├─ 存储在内存池
  └─ 减少实际 Swap I/O

启用:
  echo 1 > /sys/module/zswap/parameters/enabled

内存分配优化

内存分配器

glibc malloc

机制:
  ├─ 小内存:从堆分配(brk)
  ├─ 大内存:mmap 分配
  └─ 多线程:每线程有独立 arena

问题:
  ├─ 内存碎片
  ├─ 多线程竞争
  └─ 内存膨胀

jemalloc

特点:
  ├─ 低碎片率
  ├─ 多线程优化
  └─ 详细的内存统计

使用:
  LD_PRELOAD=/usr/lib/libjemalloc.so ./program

tcmalloc

特点:
  ├─ 高性能
  ├─ 线程缓存
  └─ Google 开发

适用场景:
  ├─ 高并发应用
  └─ 频繁内存分配

内存池技术

对象池

原理:
  ├─ 预分配固定大小对象
  ├─ 重复使用而非释放
  └─ 减少分配开销

优势:
  ├─ 消除分配释放开销
  ├─ 减少内存碎片
  └─ 提高缓存局部性

内存对齐

缓存行对齐:
  ├─ 结构体对齐到 64 字节
  ├─ 避免伪共享
  └─ 提高缓存效率

示例:
  struct alignas(64) data {
      int value;
      char padding[60];
  };

内存性能分析工具

基础工具

free

free -h

输出解读:
  total:总内存
  used:已使用(不含缓存)
  free:完全未使用
  shared:共享内存
  buff/cache:缓冲和缓存
  available:可用于新进程的内存

vmstat

vmstat 1

关键列:
  swpd:使用的 Swap
  free:空闲内存
  buff:Buffers
  cache:Cached
  si:Swap In 速率
  so:Swap Out 速率

top/htop

关键指标:
  VIRT:虚拟内存
  RES:常驻内存(物理内存)
  SHR:共享内存
  %MEM:内存使用百分比

进程内存分析

pmap

pmap -x <PID>

输出:
  Address:内存区域地址
  Kbytes:大小
  RSS:常驻内存
  Dirty:脏页
  Mode:权限
  Mapping:映射类型

smaps

cat /proc/<PID>/smaps

详细信息:
  Size:区域大小
  Rss:常驻内存
  Pss:按比例分摊的共享内存
  Private_Clean/Dirty:私有页面
  Shared_Clean/Dirty:共享页面

内存泄漏检测

valgrind memcheck

valgrind --leak-check=full \
         --show-leak-kinds=all \
         --track-origins=yes \
         ./program

检测:
  ├─ 内存泄漏
  ├─ 越界访问
  ├─ 使用未初始化内存
  └─ 使用已释放内存

AddressSanitizer(ASAN)

编译时启用:
  gcc -fsanitize=address -g program.c

优势:
  ├─ 运行时开销小(~2x)
  ├─ 检测更多错误类型
  └─ 生产环境可用(debug 版本)

heaptrack

heaptrack ./program
heaptrack --analyze heaptrack.program.*.gz

分析:
  ├─ 内存分配趋势
  ├─ 峰值内存使用
  └─ 分配热点

内存优化案例

案例1:内存泄漏排查

症状

  • 进程内存持续增长
  • 最终触发 OOM

排查步骤

  1. 确认泄漏
监控 RSS 趋势
pmap -x <PID>  # 查看内存增长区域
  1. 定位代码
valgrind --leak-check=full ./program
  1. 分析分配栈
查看 valgrind 输出的调用栈
找到分配但未释放的代码路径
  1. 修复验证
修复代码
重新测试
长期监控

案例2:页缓存不足

症状

  • 磁盘 I/O 高
  • 页缓存命中率低
  • 可用内存少

分析

vmstat 1        # 查看 bi/bo
free -h         # 查看缓存大小
sar -r 1        # 查看内存趋势

优化方案

  • 增加物理内存
  • 优化应用减少内存使用
  • 使用 mmap 减少缓存压力
  • 调整 vm.min_free_kbytes

案例3:NUMA 不平衡

症状

  • numastat 显示内存分布不均
  • 跨节点访问多

优化

绑定进程到节点:
  numactl --cpunodebind=0 --membind=0 ./program

允许内核自动平衡:
  echo 1 > /proc/sys/kernel/numa_balancing

应用改造:
  使用 numa 感知的内存分配

内存调优参数

关键内核参数

# Swap 相关
vm.swappiness = 10                    # 降低 Swap 倾向
vm.vfs_cache_pressure = 50            # 降低缓存回收压力

# 脏页相关
vm.dirty_ratio = 20                   # 脏页达到 20% 开始同步写
vm.dirty_background_ratio = 10        # 脏页达到 10% 开始后台写
vm.dirty_expire_centisecs = 3000      # 脏页 30 秒后过期
vm.dirty_writeback_centisecs = 500    # 每 5 秒唤醒刷新线程

# OOM 相关
vm.overcommit_memory = 0              # 启发式超分配
vm.panic_on_oom = 0                   # OOM 时不 panic
vm.oom_kill_allocating_task = 0       # 杀死请求分配的任务

# 大页相关
vm.nr_hugepages = 1024                # 预留大页数量
vm.hugetlb_shm_group = 1001           # 允许使用大页的组

# NUMA 相关
kernel.numa_balancing = 1             # 启用 NUMA 自动平衡

透明大页调优

# 启用方式
echo always > /sys/kernel/mm/transparent_hugepage/enabled    # 总是启用
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled   # 按需启用(推荐)
echo never > /sys/kernel/mm/transparent_hugepage/enabled     # 禁用

# 碎片整理
echo always > /sys/kernel/mm/transparent_hugepage/defrag     # 总是整理(可能卡顿)
echo madvise > /sys/kernel/mm/transparent_hugepage/defrag    # 按需整理(推荐)
echo never > /sys/kernel/mm/transparent_hugepage/defrag      # 禁用整理

下一步:学习 磁盘 I/O 性能分析与优化 章节。