Linux 性能实战 | 第 14 篇 Swap 使用分析与优化策略
Linux 性能实战 | 第 14 篇 Swap 使用分析与优化策略 💾
🔗 从 Page Fault 到 Swap:内存管理的"最后一道防线"
在第十三章中,我们深入探讨了虚拟内存机制——多级页表、TLB、Page Fault、Copy-on-Write 等核心概念。我们诊断了一个 rosbag 回放系统的性能问题,发现每秒 545 次 Major Fault 导致帧率从 30 fps 下降到 5 fps,通过 madvise() 优化后成功将 Major Fault 降至 5 次/秒。
但是,当我们谈到 Major Fault 时,实际上有两种情况:
- 文件映射(mmap):页面在文件中,需要从磁盘读取(上一章的案例)
- 匿名页(Anonymous Page):页面被 swap 到交换空间,需要 swap in
第二种情况——Swap——是本章的主角。Swap 是 Linux 内存管理的"最后一道防线",当物理内存不足时,内核会将不活跃的内存页移到磁盘上的交换空间,释放物理内存给更需要的进程使用。
本章关键问题:
- Swap 的真正作用是什么?仅仅是"内存不足时的备胎"吗?
- swappiness=60 是什么意思?是否意味着内存使用到 60% 就开始 swap?
- 如何诊断 swap 导致的性能下降?
- Zswap/zRAM 是如何提升 swap 性能的?
- 什么场景下应该完全禁用 swap?
🤔 Swap 的设计目的:不止是"内存不足"的救命稻草
1. Swap 的三大作用
很多人认为 swap 只是"内存不足时的临时方案",这是一个误解。Swap 实际上有三个重要作用:
作用 1:应急备份(最常见的理解)
- 当物理内存不足时,避免 OOM Killer 杀死进程
- 给系统一个"缓冲",防止突然的内存使用高峰
作用 2:释放不活跃页(常被忽视但很重要)
- 很多进程启动后初始化的数据很少再被访问(如配置、静态资源)
- 将这些"冷数据"swap 出去,腾出物理内存给"热数据"(如缓存)
- 即使物理内存充足,适当的 swap 也能提升性能
作用 3:休眠支持
- 笔记本电脑休眠(Hibernate)需要将内存内容保存到 swap
- 服务器通常不需要这个功能
2. 匿名页 vs 文件页
Linux 内存页分为两类:
| 类型 | 含义 | 示例 | 回收方式 |
|---|---|---|---|
| 文件页(File-backed) | 来自文件系统 | 程序代码、mmap 的文件、Page Cache | 直接丢弃(脏页需先写回) |
| 匿名页(Anonymous) | 不与文件关联 | 堆(malloc)、栈、COW 后的私有页 | 必须 swap 到交换空间 |
自动驾驶场景:
- 文件页:OpenCV 库代码、高精地图文件(mmap)
- 匿名页:点云数据缓冲区(malloc)、
std::vector的内存
📊 swappiness 参数:内核的"内存回收偏好"
1. swappiness 的真正含义
swappiness 是 Linux 内核的一个参数,取值范围 0-100,控制内核在内存回收时的"偏好":
cat /proc/sys/vm/swappiness
输出(默认):
60
常见误解:
- ❌ “swappiness=60 意味着内存使用到 60% 就开始 swap”
- ❌ “swappiness=0 完全禁用 swap”
真实含义:
swappiness 越高 → 内核越倾向于 swap 出匿名页
swappiness 越低 → 内核越倾向于回收文件页(Page Cache)
计算公式(简化):
匿名页回收权重 = swappiness
文件页回收权重 = 200 - swappiness
示例:
swappiness=60:匿名页权重 60,文件页权重 140 → 更倾向于回收文件页(2.3:1)swappiness=100:匿名页权重 100,文件页权重 100 → 同等对待(1:1)swappiness=10:匿名页权重 10,文件页权重 190 → 强烈倾向于回收文件页(19:1)
2. swappiness=0 的特殊含义
Linux 3.5 之前:
swappiness=0→ 完全禁用 swap(除非 OOM)
Linux 3.5+:
swappiness=0→ 尽量避免 swap,但在内存严重不足时仍会 swapswappiness=1→ 最小化 swap(推荐用于数据库)
验证:
# 设置 swappiness=0
sudo sysctl vm.swappiness=0
# 持久化配置
echo "vm.swappiness=0" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
3. 如何选择 swappiness 值?
| 场景 | 推荐值 | 理由 |
|---|---|---|
| 桌面系统 | 60 (默认) | 平衡性能与响应性 |
| Web 服务器 | 10-30 | 保留 Page Cache,加速文件访问 |
| 数据库服务器 | 1 | 数据库有自己的缓存,swap 会导致严重性能下降 |
| 自动驾驶计算平台 | 10-20 | 优先保留传感器数据缓存,减少匿名页 swap |
| 实时系统 | 0 + 禁用 swap | 完全避免 swap 导致的延迟抖动 |
🔍 监控 Swap 使用:vmstat、sar、/proc/meminfo
1. 使用 vmstat 实时监控
vmstat 1
输出:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 123456 2345M 234M 12.3G 0 0 234 456 123 567 15 10 70 5 0
2 1 125890 2123M 234M 12.1G 456 890 345 678 145 612 12 8 65 15 0
1 0 128234 1987M 234M 11.9G 678 1234 456 789 167 678 9 6 60 25 0
关键字段:
-
swpd:已使用的 swap 空间(KB)
- 123456 → 约 120MB
- 持续增长说明内存压力大
-
si (swap in):从 swap 读入内存的速率(KB/s)
- 456 → 每秒从 swap 读入 456KB
- 高 si 是性能杀手(磁盘读取慢)
-
so (swap out):写入 swap 的速率(KB/s)
- 890 → 每秒写入 890KB 到 swap
- 偶尔出现 so 是正常的(内核回收不活跃页)
- 持续高 so 说明内存严重不足
性能问题判断:
si > 100 KB/s → 频繁 swap in,性能严重下降
so > 500 KB/s → 频繁 swap out,内存压力大
2. 使用 sar 查看历史趋势
# 查看今天的 swap 活动
sar -W 1 10
# 查看昨天的 swap 活动
sar -W -f /var/log/sysstat/sa$(date -d yesterday +%d)
输出:
12:00:01 AM pswpin/s pswpout/s
12:00:02 AM 12.5 45.3
12:00:03 AM 89.2 234.7 ⬅️ 异常高峰
12:00:04 AM 145.6 456.9 ⬅️ 持续高 swap
Average: 45.2 123.4
- pswpin/s:每秒 swap in 的页数
- pswpout/s:每秒 swap out 的页数
3. 分析进程级 swap 使用
查看哪些进程使用了 swap:
# 查看所有进程的 swap 使用
for pid in $(ps -eo pid --no-headers); do
swap=$(grep VmSwap /proc/$pid/status 2>/dev/null | awk '{print $2}')
if [ "$swap" -gt 0 ] 2>/dev/null; then
cmd=$(ps -p $pid -o comm --no-headers)
echo "PID: $pid, Swap: ${swap}KB, Command: $cmd"
fi
done | sort -k4 -n -r | head -20
输出:
PID: 12345, Swap: 234567KB, Command: perception_node
PID: 23456, Swap: 123456KB, Command: planning_node
PID: 34567, Swap: 45678KB, Command: chrome
详细分析单个进程:
cat /proc/12345/smaps | grep -E "^(Swap|Size|Rss):" | paste - - -
输出:
Size: 1024 kB Rss: 512 kB Swap: 512 kB ⬅️ 这个内存区域有一半被 swap
Size: 4096 kB Rss: 4096 kB Swap: 0 kB ⬅️ 这个区域完全在物理内存中
Size: 2048 kB Rss: 0 kB Swap: 2048 kB ⬅️ 这个区域完全被 swap
🛠️ 实战案例:Swap 导致的自动驾驶系统性能崩溃
1. 问题现象
一个自动驾驶系统在长时间运行(8 小时)后,响应延迟从正常的 50ms 飙升到 5000ms(100 倍),系统几乎无法使用。
初步检查:
free -h
输出:
total used free shared buff/cache available
Mem: 31G 28G 500M 120M 2.5G 2.3G
Swap: 8.0G 6.2G 1.8G ⬅️ 使用了 6.2GB swap!
vmstat 1:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
5 2 6456789 512M 123M 2.3G 2345 3456 4567 5678 890 1234 5 3 2 90 0
4 3 6489012 487M 123M 2.1G 3456 4567 5678 6789 901 1345 3 2 1 94 0
关键异常:
- swap 使用 6.2GB:大量内存被 swap 出去
- si=2345 KB/s,so=3456 KB/s:频繁 swap in/out
- wa=90%:CPU 几乎全部时间在等待 I/O
- r=5, b=3:5 个进程在运行,3 个进程在等待 I/O
2. 定位问题进程
查看哪些进程被 swap:
for pid in $(ps -eo pid --no-headers); do
swap=$(grep VmSwap /proc/$pid/status 2>/dev/null | awk '{print $2}')
rss=$(grep VmRSS /proc/$pid/status 2>/dev/null | awk '{print $2}')
if [ "$swap" -gt 10000 ] 2>/dev/null; then
cmd=$(ps -p $pid -o comm --no-headers)
printf "PID: %6s, RSS: %8s KB, Swap: %8s KB, Cmd: %s\n" $pid $rss $swap "$cmd"
fi
done | sort -k8 -n -r
输出:
PID: 12345, RSS: 234567 KB, Swap: 3456789 KB, Cmd: perception_node ⬅️ 占用 3.3GB swap
PID: 23456, RSS: 123456 KB, Swap: 2345678 KB, Cmd: planning_node ⬅️ 占用 2.2GB swap
PID: 34567, RSS: 45678 KB, Swap: 456789 KB, Cmd: control_node ⬅️ 占用 446MB swap
发现:perception_node 进程的大量内存被 swap 出去!
3. 根因分析
查看进程内存分布:
cat /proc/12345/smaps | awk '/^Size:/{size=$2} /^Rss:/{rss=$2} /^Swap:/{swap=$2; if(swap>10000) print size, rss, swap}'
输出:
Size Rss Swap
2097152 512000 1585152 ⬅️ 一个 2GB 的内存区域,1.5GB 被 swap
1048576 123456 925120 ⬅️ 一个 1GB 的内存区域,900MB 被 swap
进一步检查内存使用趋势:
# 查看过去 24 小时的内存使用
sar -r -f /var/log/sysstat/sa$(date +%d) | tail -50
发现趋势:
- 系统启动时:
kbmemfree=25GB,kbswpused=0 - 运行 2 小时后:
kbmemfree=15GB,kbswpused=500MB - 运行 8 小时后:
kbmemfree=500MB,kbswpused=6.2GB
根因:
- 内存泄漏:感知进程存在内存泄漏,持续消耗内存
- 内存不足:物理内存被耗尽后,内核开始 swap
- swap 风暴:频繁的 swap in/out 导致 I/O 瓶颈,系统性能崩溃
4. 解决方案
临时方案:清理 swap
# 停止非关键进程
sudo systemctl stop unnecessary_services
# 清空 swap(需要足够的物理内存)
sudo swapoff -a
sudo swapon -a
# 验证
free -h
根本方案 1:修复内存泄漏
# 使用 valgrind 检测内存泄漏
valgrind --leak-check=full --log-file=leak.log ./perception_node
# 或使用 AddressSanitizer(需要重新编译)
g++ -fsanitize=address -g perception_node.cpp -o perception_node
./perception_node
根本方案 2:增加物理内存
# 如果确实需要这么多内存,增加物理内存
# 从 32GB 升级到 64GB
根本方案 3:调整 swappiness
# 减少 swap 倾向,优先保留 Page Cache
sudo sysctl vm.swappiness=10
# 持久化
echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf
根本方案 4:禁用 swap(适合实时系统)
# 完全禁用 swap
sudo swapoff -a
# 从 fstab 中移除 swap 配置
sudo sed -i '/swap/d' /etc/fstab
5. 优化效果
修复内存泄漏 + 调整 swappiness 后:
free -h
优化后:
total used free shared buff/cache available
Mem: 31G 12G 8G 120M 11G 18G
Swap: 8.0G 0B 8.0G ⬅️ swap 使用为 0
vmstat 1:
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
2 0 0 8.2G 456M 10.5G 0 0 123 234 456 789 45 15 38 2 0
性能恢复:
- 响应延迟:5000ms → 45ms(恢复 99%)
- CPU iowait:90% → 2%
- Swap 使用:6.2GB → 0
🚀 Zswap 与 zRAM:内存压缩技术
1. Zswap:压缩内存缓存
传统 swap 流程:
匿名页 → swap 到磁盘 → 读取延迟 ~5ms
Zswap 优化流程:
匿名页 → 压缩后存储在内存池 → 读取延迟 ~50μs(快 100 倍)
→ 内存池满时才 swap 到磁盘
启用 Zswap:
# 检查是否支持 Zswap
cat /sys/module/zswap/parameters/enabled
# 输出:Y 表示已启用
# 启用 Zswap(内核启动参数)
# 编辑 /etc/default/grub
GRUB_CMDLINE_LINUX="zswap.enabled=1 zswap.compressor=lz4 zswap.max_pool_percent=20"
# 更新 grub 并重启
sudo update-grub
sudo reboot
参数说明:
zswap.enabled=1:启用 Zswapzswap.compressor=lz4:使用 LZ4 压缩算法(快速)zswap.max_pool_percent=20:Zswap 池最多占用 20% 物理内存
压缩比:
- LZ4:压缩比 ~2:1,速度最快
- LZO:压缩比 ~2.5:1,速度较快
- ZSTD:压缩比 ~3:1,速度中等
2. zRAM:虚拟 swap 设备
zRAM 原理:
- 创建一个虚拟的块设备(如
/dev/zram0) - 数据写入时自动压缩存储在内存中
- 完全避免磁盘 I/O
启用 zRAM:
# 安装 zram-tools(Ubuntu/Debian)
sudo apt install zram-tools
# 配置 zRAM
sudo nano /etc/default/zramswap
# 设置 zRAM 大小为物理内存的 50%
ALGO=lz4
PERCENT=50
# 启动 zRAM
sudo systemctl restart zramswap
# 验证
swapon --show
输出:
NAME TYPE SIZE USED PRIO
/dev/zram0 partition 16G 0B 100 ⬅️ zRAM swap(优先级高)
/dev/sda2 partition 8G 0B -2 ⬅️ 传统 swap(优先级低)
优势:
- 完全避免磁盘 I/O,延迟降低 100 倍
- 压缩后节省内存(压缩比 2-3:1)
- 适合内存紧张的系统(如嵌入式、容器)
劣势:
- CPU 开销(压缩/解压)
- 无法用于休眠(Hibernate)
自动驾驶场景:
- 边缘计算单元(内存 < 8GB):推荐使用 zRAM
- 主计算平台(内存 > 32GB):可选 Zswap
⛔ 何时应该禁用 Swap?
1. 应该禁用 swap 的场景
| 场景 | 理由 | 配置 |
|---|---|---|
| 数据库服务器 | 数据库有自己的缓存机制,swap 会导致严重性能下降 | swappiness=1 或禁用 |
| 实时系统 | swap 导致的延迟抖动不可接受(自动驾驶控制) | 完全禁用 + mlockall() |
| 高性能计算 | swap 会导致性能下降 10-100 倍 | 禁用或 swappiness=0 |
| 容器环境 | 容器内存限制已由 cgroup 控制 | 禁用(宿主机可保留) |
| 内存充足系统 | 物理内存 > 实际需求 2 倍 | 可禁用或保留少量 swap |
2. 应该保留 swap 的场景
| 场景 | 理由 | 推荐配置 |
|---|---|---|
| 桌面系统 | 休眠(Hibernate)功能需要 swap | swappiness=60 |
| Web 服务器 | 应对突发流量,避免 OOM | swappiness=10-30 |
| 内存受限系统 | 物理内存不足(< 8GB) | swappiness=30 + zRAM |
| 多用户系统 | 用户数量不可控 | swappiness=30-60 |
3. 实时系统的内存锁定
自动驾驶控制节点:必须避免 swap 导致的延迟抖动
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
// 锁定所有当前和未来的内存页,防止被 swap
if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
perror("mlockall failed");
return 1;
}
printf("All memory locked, no swap allowed\n");
// 实时控制逻辑
while (true) {
// 处理传感器数据
// 计算控制指令
// 保证延迟 < 10ms,无抖动
}
munlockall();
return 0;
}
注意事项:
mlockall()需要CAP_IPC_LOCK权限或 root- 确保进程不会无限增长(内存泄漏会导致 OOM)
💾 Swap 分区 vs Swap 文件
1. 性能对比
传统观点:swap 分区比 swap 文件快
现代现实(Linux 2.6.29+):
- 性能几乎相同(差异 < 5%)
- 文件系统层的缓存抵消了额外开销
基准测试:
# 测试 swap 分区
time dd if=/dev/zero of=/dev/sda2 bs=1M count=1024
# 测试 swap 文件
time dd if=/dev/zero of=/swapfile bs=1M count=1024
结果:
Swap 分区:1.23 秒
Swap 文件:1.28 秒(慢 4%)
2. 管理灵活性对比
| 特性 | Swap 分区 | Swap 文件 |
|---|---|---|
| 创建难度 | 需要重新分区(可能导致数据丢失) | dd + mkswap 即可 |
| 调整大小 | 非常困难 | 轻松调整(删除旧文件,创建新文件) |
| 迁移 | 需要备份、重新分区 | 复制文件即可 |
| 多个 swap | 需要多个分区 | 可以创建多个文件 |
| SSD 磨损 | 固定位置,磨损集中 | 文件系统分散写入,磨损均衡 |
3. 推荐配置
桌面/笔记本:
# 创建 8GB swap 文件
sudo dd if=/dev/zero of=/swapfile bs=1G count=8
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# 持久化
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
服务器(自动驾驶计算平台):
# 禁用 swap(实时系统)
sudo swapoff -a
sudo sed -i '/swap/d' /etc/fstab
# 或保留小量 swap 用于应急(非实时节点)
# 创建 2GB swap 文件
sudo dd if=/dev/zero of=/swapfile bs=1G count=2
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# 设置低 swappiness
sudo sysctl vm.swappiness=1
echo "vm.swappiness=1" | sudo tee -a /etc/sysctl.conf
📝 总结与最佳实践
核心要点
- Swap 不只是"内存不足的备胎":它还能释放不活跃页,提高缓存效率
- swappiness 控制回收偏好:不是"内存使用百分比",而是"匿名页 vs 文件页"的权重
- 监控 si/so 比 swap 使用率更重要:频繁 swap in/out 才是性能杀手
- Zswap/zRAM 通过压缩提升性能:延迟从 5ms 降至 50μs(快 100 倍)
- 实时系统应禁用 swap:使用
mlockall()锁定内存 - swap 文件 vs 分区:现代系统性能相近,文件更灵活
Swap 诊断与优化清单
✅ 监控 Swap 活动
# 实时监控 swap in/out
vmstat 1
# 查看历史趋势
sar -W
# 查看进程级 swap 使用
grep VmSwap /proc/*/status | grep -v " 0 kB" | sort -k2 -n -r
✅ 优化 swappiness
# 桌面系统(默认)
vm.swappiness=60
# Web/应用服务器(保留缓存)
vm.swappiness=10
# 数据库服务器(最小化 swap)
vm.swappiness=1
# 实时系统(禁用 swap)
swapoff -a
✅ 启用内存压缩
# Zswap(内核 3.11+)
# 编辑 /etc/default/grub
GRUB_CMDLINE_LINUX="zswap.enabled=1 zswap.compressor=lz4"
# zRAM(嵌入式/容器)
sudo apt install zram-tools
# 编辑 /etc/default/zramswap: PERCENT=50
自动驾驶系统 Swap 策略
| 节点类型 | 内存 | Swap 配置 | swappiness | 理由 |
|---|---|---|---|---|
| 实时控制 | 4-8GB | 禁用 + mlockall() |
N/A | 避免延迟抖动 |
| 感知算法 | 16-32GB | 2GB swap 文件 | 10 | 应急备份,优先缓存 |
| 数据采集 | 32-64GB | 4GB swap 文件 | 10 | 应对突发流量 |
| 边缘单元 | 4-8GB | 4GB zRAM | 30 | 内存受限,压缩优化 |
🎯 下一章预告
在本章中,我们深入理解了 Swap 的真正作用——不仅是内存不足时的"救命稻草",还能通过释放不活跃页提升系统性能。我们掌握了 swappiness 参数的真实含义,学会了使用 vmstat、sar 诊断 swap 导致的性能问题,并探讨了 Zswap/zRAM 等内存压缩技术。
在下一章《磁盘 IO 性能分析与瓶颈定位》中,我们将深入存储子系统的性能分析:
- IOPS、吞吐量、延迟:三大 IO 性能指标的关系与权衡
- 顺序 IO vs 随机 IO:为什么性能差异可达 100 倍?
- IO 调度器:noop、deadline、cfq、mq-deadline、bfq、kyber 的选择
- 队列深度与并发度:如何充分发挥 SSD 性能
- iostat、iotop、blktrace:磁盘性能分析工具链
- NVMe vs SATA SSD vs HDD:不同存储介质的性能特征
通过真实的自动驾驶数据采集和回放场景,我们将揭示存储 IO 对系统性能的深远影响。敬请期待!🚀
更多推荐

所有评论(0)