内存吃紧时,冷页面常被送去慢速磁盘;zram 则用压缩的内存替代这一步,用小部分 CPU 换取可观的内存空间。
内存里的压缩块设备
zram 是一块驻留在内存里的块设备,它不跟磁盘对话,而是先在内存里把换出的页压缩、保存起来。内核把一页换给它时,页先被压缩、留在内存中,于是内存方面的减压方式并不是「把页挪去磁盘」,而是「让这一页变小」。因为设备本身挂在内存上,读写它的速度远比磁盘后备 swap 快;可它的容量同样受制约——既取决于当前这页有多可压缩,也取决于你愿意拿多少真实内存去换这块 swap。
压缩就是这笔交易
「压缩」就是这笔交易赚头所在,也是「一台小机型显得能装下比物理内存更多内容」的根源。一页值不值得压要看内容:全零页、文本和有结构的数据都缩得不错,而图片、压缩包这类已经被压缩过的对象几乎缩不动。站在工程视角看,zram 等于把暂时闲置的 CPU 换算成「看似更多」的内存;当初工作负责多半是可压缩的、又特别想避开真实磁盘换页的延迟时,这笔交易才划算。
适合服务器与低端设备
它最合身的地方是那些内存不多、换页压力又不过度的机器:小 VPS、容器宿主机,以及手机、路由器这类如今要把袖珍机器安排得明明白白的角色。反过来,内存本来就充裕、或者本来就要换到冷冷盘上的服务器,同一笔钱买一张真实磁盘往往能装下更多,因此未必轮到 zram。实用的判断方法是盯住压缩率和 swap 使用量:当驻留数据始终很小、又够可压缩、且 CPU 相对空闲,才是让 zram 出场的时刻。
坏掉的样子
最常见的失望是压缩比根本没有到来。照片、视频、加密缓冲和已经压过的归档几乎压不动,mm_stat 里 orig_data_size 只比 compr_data_size 大一点,交换空间却照样被占满。内存池和元数据照样吃掉真实内存,腾出来的余量几乎为零,杀手来得和没开压缩交换一样快。压缩比是数据的属性,不是设备的属性,容量只能按实测值去估。
选错算法是死在处理器上,不是死在容量上。重型算法放在小内存虚拟机或旧手机上,每次换出都要烧大量周期,回收线程转个不停,系统态占用随压力一起涨,交换还没满,长尾延迟先垮了。交互型机器先选 lzo-rle、lz4 或低档 zstd 这类快速算法,换算法前一定要在真实负载下看占用和延迟。
同时打开 zswap 和压缩交换等于把同一页压两遍,白白浪费处理器。zswap 自己先留一块压缩池,再把已经压过的页交给压缩设备,后者几乎找不到可省的空间。两条路径一起烧处理器,交换看似用了不少,有效容量却没涨多少。一条交换路径只留一层压缩:要么把压缩设备当交换,要么让 zswap 走在磁盘交换前面,不要双开。
写死的容量是最安静的错配。某台机器从大内存规格缩到小规格,单元文件里写死的几吉字节不变,压缩池一下占掉近半内存,杀手来得更快;反过来从小规格升到大规格,旧的几吉字节又白白浪费了余量。容量在创建交换时就定死,不会跟着内存热插拔或实例变配走。应该在开机时按内存比例算出容量,变配后复查设备大小并记录在部署说明里。
快速验证
把下面这段存成 zram_ratio.py,放在临时目录里跑 python3 zram_ratio.py。它只用标准库,以只读方式打开 /sys/block/zram*/mm_stat,不需要特权,也不做任何写入。脚本解析每台设备原始字节与压缩后字节,用两者相除算出压缩比和节省比例。机器上没有压缩设备时打印 SKIP,转而用同一套公式给一组示例数字算一遍,保证在任何机器上都能跑通。
#!/usr/bin/env python3
"""只读的 zram 压缩比检查(仅标准库)。"""
import glob
from pathlib import Path
def parse_mm_stat(text):
parts = text.split()
if len(parts) < 2:
raise ValueError("mm_stat 至少需要原始与压缩两个字段")
orig = int(parts[0])
compr = int(parts[1])
rest = [int(x) for x in parts[2:]]
return orig, compr, rest
def report(device, orig, compr):
if compr <= 0:
print("%s: orig=%d compr=%d ratio=n/a (empty)" % (device, orig, compr))
return
ratio = orig / compr
saved = 100.0 * (1.0 - compr / orig) if orig > 0 else 0.0
print("%s: orig=%d compr=%d ratio=%.2f saved=%.1f%%" % (device, orig, compr, ratio, saved))
def main():
paths = sorted(glob.glob("/sys/block/zram*/mm_stat"))
if not paths:
print("SKIP: 找不到 /sys/block/zram*/mm_stat(无 zram?非 Linux?)")
orig, compr = 300 * 1024 * 1024, 100 * 1024 * 1024
print("用同一套公式给示例数字计算:")
report("example/zram0", orig, compr)
print("PASS: 比例公式可用;在 zram 主机上上面会换成实时行")
return
for path in paths:
try:
text = Path(path).read_text(encoding="utf-8")
except OSError as exc:
print("SKIP: 无法读取 %s(%s)" % (path, exc))
continue
try:
orig, compr, _ = parse_mm_stat(text)
except (ValueError, IndexError) as exc:
print("SKIP: 无法解析 %s(%s)" % (path, exc))
continue
device = path.split("/mm_stat")[0]
report(device, orig, compr)
print("PASS: 已展示实时统计;比例走低加池满意味着改容量或换算法")
if __name__ == "__main__":
main()
在打开压缩交换的机器上,它会逐台打印原始字节、压缩后字节、压缩比、内存占用和上限,压缩比一路走低或内存池打满都能在杀手到来前看到。在普通笔记本或没有设备的容器里,它先打印 SKIP 再展示示例计算,示例里三比一的比例对应节省约三分之二。它只验证统计口径和算术,不证明算法和容量选得对,调优前还要对照单元文件复查算法名和容量比例。
延伸阅读
来源
- kernel.org:zram 块设备,zram 的权威文档,已核验 2026-11-23。
- Wikipedia:zram,zram 是什么、怎样运作,已核验 2026-11-23。
- ArchWiki:Zram,实际配置与调优,已核验 2026-11-23。