在 Ubuntu(Linux)下,readdir()(以及底层 getdents() 系统调用)用于读取目录内容。如果目录很大或调用频繁,性能容易成为瓶颈。下面从原理 → 常见瓶颈 → 优化手段系统说明。
opendir() → 打开目录readdir() → 每次返回一个目录项(用户态缓冲)getdents64()性能主要消耗在:
while ((de = readdir(dir))) {
stat(de->d_name, &st); // 非常慢
}
每次 stat 都会:
✅ 优化:
statreaddir + d_type 判断类型(不能是未知类型)if (de->d_type == DT_REG) { ... }
⚠️ 注意:d_type 在部分 FS(如某些 NFS)可能不可用
readdir_r(线程安全,但非必须更快)一般没必要,现代 readdir 已线程安全(glibc)
getdents64 直接读取(高级)减少 libc 封装开销:
#include <sys/syscall.h>
#include <linux/dirent.h>
syscall(SYS_getdents64, fd, buf, size);
适合:
opendir 默认 buffer 较小,可手动设置:
DIR *dir = opendir(path);
int fd = dirfd(dir);
fcntl(fd, F_SETBUFSIZE, 1 << 20); // 1MB
或通过 getdents 直接指定大 buffer
性能对比(大致):
| 文件系统 | readdir 性能 |
|---|---|
| ext4 | ✅ 好 |
| xfs | ✅✅ 更好(大目录) |
| btrfs | ❌ 慢(大量小文件) |
| overlayfs | ❌ 明显慢 |
| NFS | ❌❌ 非常慢 |
✅ 如果目录巨大:
这是最有效的方式:
/data/a/b/c/xxx
多个线程读不同目录可以:
dir1/
dir2/
dir3/
❌ 不要多线程读同一目录(锁竞争)
scandir(一次性读入内存)适合中小目录:
struct dirent **namelist;
int n = scandir(path, &namelist, NULL, alphasort);
优点:
sudo sysctl -w vm.vfs_cache_pressure=50
减少 inode 回收
挂载时:
mount -o noatime /dev/sdX /mnt
减少元数据写
ionice / niceionice -c 3 readdir_program
避免影响线上服务
strace -e trace=getdents,stat your_program
perf top -p PID
iostat -x 1
✅ 最快做法:
❌ 最慢做法:
如果你愿意,可以告诉我:
stat我可以给你针对性代码或架构建议。