2. 存储硬件基础

Android 设备的存储性能,首先由硬件介质决定。应用层的数据库、文件读写、日志落盘、媒体缓存,最终都要经过文件系统、块层、驱动,落到 eMMC、UFS、NVMe 或 SD 卡等物理介质上。理解这些介质的读写原理与性能边界,是后续 IO 调优的前提:如果瓶颈在硬件本身,再多的软件优化也只能逼近其上限,而无法突破。

一、核心原理:从 NAND 闪存到块设备

无论 eMMC、UFS 还是 NVMe SSD,其存储核心都是 NAND Flash。NAND 的物理特性决定了三条铁律:读以页为单位、写以页为单位、擦除以块为单位。典型 NAND 的页大小为 4KB~16KB,块由数百个页组成。写入时只能把 1 写成 0,要把 0 变回 1 必须先擦除整块。因此,当需要修改一个已写入的页时,控制器不能原地覆盖,只能把新数据写到新的空闲页,并把旧页标记为无效,这个过程称为写放大。

为了掩盖 NAND 的擦写限制,控制器内部运行 FTL(Flash Translation Layer),通过逻辑地址到物理地址的映射,把块设备接口暴露给上层。同时,磨损均衡、垃圾回收(GC)、坏块管理都在控制器内完成。垃圾回收会在后台搬运有效页、擦除整块,这会与前台 IO 争抢带宽,造成偶发的延迟尖峰——这正是很多“卡顿”问题的硬件根源。

关键结论:随机写性能远低于顺序写,因为随机写会触发更多页映射更新与垃圾回收;而随机读通常接近顺序读,因为读不需要擦除。

二、四类介质的结构与特性对比

eMMC 是“NAND + 控制器”封装为一颗 BGA 芯片,通过并行总线与 SoC 相连,协议相对简单,成本低,常见于入门机型。UFS 在 eMMC 基础上引入串行差分链路与命令队列(Command Queue),支持全双工读写,即读写可以同时进行,并支持 HS-GEAR 速率等级,性能大幅提升,是当前中高端 Android 设备的主流。NVMe 则是面向 PCIe 总线的协议,拥有极深的队列深度与极低协议开销,在手机端多以 UFS 卡或板载形式出现,在平板、车机、笔记本形态中更常见。SD 卡是可插拔介质,通过 SDIO 或 UHS 接口接入,速度等级参差,且热插拔、供电与协议开销都带来额外不确定性。

Android 存储介质结构与性能定位 eMMC 并行总线 · 半双工 单命令队列 成本低 · 入门机型 UFS 串行差分 · 全双工 命令队列 CQ 中高端主流 NVMe PCIe · 深队列 低协议开销 平板/车机/笔电 SD 卡 可插拔 · SDIO/UHS 速度等级差异大 热插拔不确定 顺序读带宽量级(示意) eMMC ~300MB/s UFS 3.x ~2GB/s NVMe PCIe3 x4 ~3.5GB/s SD UHS-I ~100MB/s NAND 读写擦原理与写放大 读:按页 4~16KB 写:按页,先擦后写 擦:按块,数百页 改页 → 搬移 → GC → 写放大 随机写触发频繁 GC,延迟尖峰;顺序写与随机读更接近介质上限

三、性能指标:看懂规格书与实测数据

评估存储介质时,不能只看“容量”和“最大顺序读”。真正影响 Android 体验的指标包括:

  • 顺序读写带宽:影响大文件拷贝、视频录制、应用安装解压。
  • 随机读写 IOPS:4KB 随机读写在数据库、SharedPreferences、日志场景中占主导,直接决定应用启动与列表滑动流畅度。
  • 访问延迟:单次 IO 的响应时间,UFS 与 NVMe 的队列机制能显著降低高并发下的平均延迟。
  • 队列深度与并发:UFS 的 CQ、NVMe 的多队列,允许更多请求并行,提升吞吐。
  • 耐久度(TBW/DWPD):写入寿命,影响长期使用后的掉速与可靠性。
  • 热稳定性:高负载下控制器降频,性能会明显回落。
介质 接口/协议 典型顺序读 典型随机读 并发能力 典型场景
eMMC 5.1 并行 · 半双工 约 250~400MB/s 约 5K~15K IOPS 弱 入门手机、电视盒子
UFS 2.2 串行 · 全双工 · CQ 约 800MB/s~1GB/s 约 30K~60K IOPS 中 中端手机
UFS 3.1/4.0 串行 · 全双工 · CQ 约 1.8~4GB/s 约 80K~200K IOPS 强 旗舰手机、平板
NVMe SSD PCIe · 多队列 约 3~7GB/s 约 200K~1M IOPS 很强 车机、笔电、服务器
SD UHS-I SDIO · 半双工 约 90~104MB/s 约 1K~3K IOPS 弱 外置扩展、相机

四、方法流程:如何定位与验证存储瓶颈

在真机调优时,建议按以下流程确认硬件是否成为瓶颈:

  1. 先用 fio 或 AndroBench 做基线测试,分别测顺序读写与 4KB 随机读写。
  2. 用 iostat、blktrace 观察 IO 队列深度、await 延迟与 %util。
  3. 对比应用实际 IO 模式:是大量小随机写,还是大块顺序读。
  4. 若随机写 IOPS 已接近介质上限,应优先改软件策略(批量写、合并、异步),而非继续压硬件。
# 4KB 随机写测试示例
fio --name=randwrite --ioengine=libaio --direct=1 \
    --rw=randwrite --bs=4k --size=256M --numjobs=4 \
    --iodepth=32 --runtime=30 --group_reporting

# 查看块设备 IO 统计
iostat -x 1

五、案例与场景:同一份代码,不同介质差异巨大

某日志组件在旗舰 UFS 3.1 机型上每秒写入 2000 条小记录毫无压力,但在 eMMC 5.1 的入门机上出现明显卡顿。原因在于:每条日志都是一次 4KB 随机写,eMMC 的随机写 IOPS 只有 UFS 的十分之一左右,且半双工无法读写并行,GC 一触发,前台写入延迟从毫秒级跳到几十毫秒。优化手段不是换硬件,而是把日志改为内存缓冲 + 批量顺序落盘,把随机写转成顺序写,卡顿随即消失。

选型影响:如果应用面向低端机,必须假设随机写能力很弱,尽量采用顺序追加、批量提交、异步落盘;如果面向旗舰机,可以更激进地使用数据库与频繁小文件写,但仍要避免无节制的 fsync。

六、常见误区与复盘

  • 只看顺序读速度:顺序读高不代表随机写好,而 Android 日常 IO 以随机小写为主。
  • 把 SD 卡当内部存储用:SD 卡随机性能差、热插拔风险高,不适合数据库与频繁写日志。
  • 忽视 GC 与热降速:长时间高负载后性能回落是正常现象,测试要覆盖持续写入场景。
  • 用错测试工具:不带 direct=1 的测试会被页缓存掩盖真实介质性能。
复盘要点:硬件介质决定了 IO 性能的上限,软件优化只能逼近上限。调优前先确认介质类型与实测随机 IOPS,再决定优化方向,避免在已经饱和的硬件上做无效的代码微调。