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 时,实际上有两种情况:

  1. 文件映射(mmap):页面在文件中,需要从磁盘读取(上一章的案例)
  2. 匿名页(Anonymous Page):页面被 swap 到交换空间,需要 swap in

第二种情况——Swap——是本章的主角。Swap 是 Linux 内存管理的"最后一道防线",当物理内存不足时,内核会将不活跃的内存页移到磁盘上的交换空间,释放物理内存给更需要的进程使用。

本章关键问题

  • Swap 的真正作用是什么?仅仅是"内存不足时的备胎"吗?
  • swappiness=60 是什么意思?是否意味着内存使用到 60% 就开始 swap?
  • 如何诊断 swap 导致的性能下降?
  • Zswap/zRAM 是如何提升 swap 性能的?
  • 什么场景下应该完全禁用 swap?

🤔 Swap 的设计目的:不止是"内存不足"的救命稻草

1. Swap 的三大作用

很多人认为 swap 只是"内存不足时的临时方案",这是一个误解。Swap 实际上有三个重要作用:

Swap 的三大作用

作用 1
🆘 应急备份
内存不足时避免 OOM

作用 2
🧹 释放不活跃页
提高活跃页的缓存命中率

作用 3
🔄 休眠支持
Hibernate 将内存保存到 swap

避免系统崩溃

提升系统性能

节能与快速恢复

作用 1:应急备份(最常见的理解)

  • 当物理内存不足时,避免 OOM Killer 杀死进程
  • 给系统一个"缓冲",防止突然的内存使用高峰

作用 2:释放不活跃页(常被忽视但很重要)

  • 很多进程启动后初始化的数据很少再被访问(如配置、静态资源)
  • 将这些"冷数据"swap 出去,腾出物理内存给"热数据"(如缓存)
  • 即使物理内存充足,适当的 swap 也能提升性能

作用 3:休眠支持

  • 笔记本电脑休眠(Hibernate)需要将内存内容保存到 swap
  • 服务器通常不需要这个功能

2. 匿名页 vs 文件页

Linux 内存页分为两类:

类型 含义 示例 回收方式
文件页(File-backed) 来自文件系统 程序代码、mmap 的文件、Page Cache 直接丢弃(脏页需先写回)
匿名页(Anonymous) 不与文件关联 堆(malloc)、栈、COW 后的私有页 必须 swap 到交换空间

内存回收策略

干净页

脏页

必须

文件页

直接丢弃

写回磁盘
然后丢弃

匿名页

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)

swappiness=100

匿名页
回收概率 50%

文件页
回收概率 50%

swappiness=60 (默认)

匿名页
回收概率 30%

文件页
回收概率 70%

swappiness=10

匿名页
回收概率 5%

文件页
回收概率 95%

2. swappiness=0 的特殊含义

Linux 3.5 之前

  • swappiness=0 → 完全禁用 swap(除非 OOM)

Linux 3.5+

  • swappiness=0 → 尽量避免 swap,但在内存严重不足时仍会 swap
  • swappiness=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=25GBkbswpused=0
  • 运行 2 小时后:kbmemfree=15GBkbswpused=500MB
  • 运行 8 小时后:kbmemfree=500MBkbswpused=6.2GB

根因

  1. 内存泄漏:感知进程存在内存泄漏,持续消耗内存
  2. 内存不足:物理内存被耗尽后,内核开始 swap
  3. 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

池满时

访问时

应用程序
4GB 匿名页

内核

Swap 到磁盘
延迟 ~5ms

压缩到 Zswap 池
4GB → 1GB
延迟 ~50μs

解压到内存
延迟 ~50μs

启用 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:启用 Zswap
  • zswap.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
渲染错误: Mermaid 渲染失败: Setting zRAM as parent of zRAM would create a cycle

启用 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 对系统性能的深远影响。敬请期待!🚀
在这里插入图片描述

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐