BMD*_*Dan 118 ext3 directory kernel rm
背景:物理服务器,大约两年,7200-RPM SATA 驱动器连接到 3Ware RAID 卡,ext3 FS 挂载 noatime 和 data=ordered,不在疯狂负载下,内核 2.6.18-92.1.22.el5,正常运行时间 545 天. 目录不包含任何子目录,只有数百万个小(~100 字节)文件,还有一些更大的(几 KB)文件。
我们有一台服务器在过去几个月中出现了一些问题,但我们直到前几天才注意到它,因为它开始无法写入目录,因为它包含太多文件。具体来说,它开始在 /var/log/messages 中抛出这个错误:
ext3_dx_add_entry: Directory index full!
Run Code Online (Sandbox Code Playgroud)
有问题的磁盘有大量剩余的 inode:
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sda3 60719104 3465660 57253444 6% /
Run Code Online (Sandbox Code Playgroud)
所以我猜这意味着我们达到了目录文件本身可以有多少条目的限制。不知道会有多少文件,但正如您所见,不能超过三百万左右。不是那么好,请注意!但这是我的问题之一:上限到底是多少?是可调的吗?在我被骂之前——我想把它调低;这个庞大的目录导致了各种各样的问题。
无论如何,我们在生成所有这些文件的代码中找到了问题,我们已经更正了它。现在我坚持删除目录。
这里有几个选项:
rm -rf (dir)
我先试过这个。在它运行了一天半没有任何明显影响后,我放弃并杀死了它。
while [ true ]; do ls -Uf | head -n 10000 | xargs rm -f 2>/dev/null; done )
这实际上是缩短的版本;我正在运行的真正的一个,它只是添加了一些进度报告和一个干净的停止,当我们用完要删除的文件时,是:
出口 i=0; 时间 ( while [ true ]; 做 ls -Uf | 头-n 3 | grep -qF '.png' || 休息; ls -Uf | 头-n 10000 | xargs rm -f 2>/dev/null; 出口 i=$(($i+10000)); echo "$i..."; 完毕 )
这似乎工作得很好。在我写这篇文章时,它在过去三十分钟左右删除了 260,000 个文件。
ls -U删除前 10,000 个条目可能需要十分钟#3 中的命令,但现在它很愉快地进行了?就此而言,它在大约 30 分钟内删除了 260,000 个,但现在又花了 15 分钟才删除了 60,000 个。为什么速度波动很大?find,由于几个不言而喻的原因,这些变化不会比我的方法快得多。但是通过 fsck 删除的想法有什么用吗?或者完全是别的什么?我渴望听到开箱即用(或在不知名的盒子内)的想法。最终脚本输出!:
2970000...
2980000...
2990000...
3000000...
3010000...
real 253m59.331s
user 0m6.061s
sys 5m4.019s
Run Code Online (Sandbox Code Playgroud)
因此,在四个多小时内删除了 300 万个文件。
Mat*_*Ife 89
2021 年 8 月更新
这个答案继续吸引了很多关注,我觉得它似乎已经过时了,现在有点多余了。
执行 afind ... -delete最有可能在性能方面产生可接受的结果。
我认为可能会导致更高性能的一个方面是解决问题的“删除”部分而不是“列表”部分。
我试过了,没有用。但我觉得解释我做了什么以及为什么这样做很有用。
在当今较新的内核中,通过使用内核中的 IO uring 子系统(请参阅 参考资料man 2 io_uring_setup),实际上可以尝试异步执行取消链接 —— 这意味着我们可以提交取消链接请求,而无需等待或阻塞以查看结果。
该程序基本上读取一个目录,提交数百个unlinks而不等待结果,然后在系统完成处理请求后获取结果。
它试图做它dentls所做的但使用 IO uring。可以用gcc -o dentls2 dentls2.c -luring.
#include <stdlib.h>
#include <stdint.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <err.h>
#include <sched.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <linux/io_uring.h>
#include <liburing.h>
/* Try to keep the queue size to under two pages as internally its stored in
* the kernel as contiguously ordered pages. Basically the bigger you make it
* the higher order it becomes and the less likely you'll have the contiguous
* pages to support it, despite not hitting any user limits.
* This reduces an ENOMEM here by keeping the queue size as order 1
* Ring size internally is rougly 24 bytes per entry plus overheads I haven't
* accounted for.
*/
#define QUEUE_SIZE 256
/* Globals to manage the queue */
static volatile int pending = 0;
static volatile int total_files = 0;
/* Probes kernel uring implementation and checks if action is
* supported inside the kernel */
static void probe_uring(
struct io_uring *ring)
{
struct io_uring_probe *pb = {0};
pb = io_uring_get_probe_ring(ring);
/* Can we perform IO uring unlink in this kernel ? */
if (!io_uring_opcode_supported(pb, IORING_OP_UNLINKAT)) {
free(pb);
errno = ENOTSUP;
err(EXIT_FAILURE, "Unable to configure uring");
}
free(pb);
}
/* Place a unlink call for the specified file/directory on the ring */
static int submit_unlink_request(
int dfd,
const char *fname,
struct io_uring *ring)
{
char *fname_cpy = strdup(fname);
struct io_uring_sqe *sqe = NULL;
/* Fetch a free submission entry off the ring */
sqe = io_uring_get_sqe(ring);
if (!sqe)
/* Submission queue full */
return 0;
pending++;
/* Format the unlink call for submission */
io_uring_prep_rw(IORING_OP_UNLINKAT, sqe, dfd, fname_cpy, 0, 0);
sqe->unlink_flags = 0;
/* Set the data to just be the filename. Useful for debugging
* at a later point */
io_uring_sqe_set_data(sqe, fname_cpy);
return 1;
}
/* Submit the pending queue, then reap the queue
* clearing up room on the completion queue */
static void consume_queue(
struct io_uring *ring)
{
char *fn;
int i = 0, bad = 0;
int rc;
struct io_uring_cqe **cqes = NULL;
if (pending < 0)
abort();
cqes = calloc(pending, sizeof(struct io_uring_cqe *));
if (!cqes)
err(EXIT_FAILURE, "Cannot find memory for CQE pointers");
/* Notify about submitted entries from the queue (this is a async call) */
io_uring_submit(ring);
/* We can immediately take a peek to see if we've anything completed */
rc = io_uring_peek_batch_cqe(ring, cqes, pending);
/* Iterate the list of completed entries. Check nothing crazy happened */
for (i=0; i < rc; i++) {
/* This returns the filename we set earlier */
fn = io_uring_cqe_get_data(cqes[i]);
/* Check the error code of the unlink calls */
if (cqes[i]->res < 0) {
errno = -cqes[i]->res;
warn("Unlinking entry %s failed", fn);
bad++;
}
/* Clear up our CQE */
free(fn);
io_uring_cqe_seen(ring, cqes[i]);
}
pending -= rc + bad;
total_files += rc - bad;
free(cqes);
}
/* Main start */
int main(
const int argc,
const char **argv)
{
struct io_uring ring = {0};
struct stat st = {0};
DIR *target = NULL;
int dfd;
struct dirent *fn;
/* Check initial arguments passed make sense */
if (argc < 2)
errx(EXIT_FAILURE, "Must pass a directory to remove files from.");
/* Check path validity */
if (lstat(argv[1], &st) < 0)
err(EXIT_FAILURE, "Cannot access target directory");
if (!S_ISDIR(st.st_mode))
errx(EXIT_FAILURE, "Path specified must be a directory");
/* Open the directory */
target = opendir(argv[1]);
if (!target)
err(EXIT_FAILURE, "Opening the directory failed");
dfd = dirfd(target);
/* Create the initial uring for handling the file removals */
if (io_uring_queue_init(QUEUE_SIZE, &ring, 0) < 0)
err(EXIT_FAILURE, "Cannot initialize URING");
/* Check the unlink action is supported */
probe_uring(&ring);
/* So as of writing this code, GETDENTS doesn't have URING support.
* but checking the kernel mailing list indicates its in progress.
* For now, we'll just do laymans readdir(). These days theres no
* actual difference between it and making the getdents() call ourselves.
*/
while (fn = readdir(target)) {
if (fn->d_type != DT_REG)
/* Pay no attention to non-files */
continue;
/* Add to the queue until its full, try to consume it
* once its full.
*/
while (!submit_unlink_request(dfd, fn->d_name, &ring)) {
/* When the queue becomes full, consume queued entries */
consume_queue(&ring);
/* This yield is here to give the uring a chance to
* complete pending requests */
sched_yield();
continue;
}
}
/* Out of files in directory to list. Just clear the queue */
while (pending) {
consume_queue(&ring);
sched_yield();
}
printf("Total files: %d\n", total_files);
io_uring_queue_exit(&ring);
closedir(target);
exit(0);
}
Run Code Online (Sandbox Code Playgroud)
具有讽刺意味的是,结果与我的猜测相反,但为什么呢?
$ time ./dentls2 /tmp/many
Total files: 4000000
real 0m6.459s
user 0m0.360s
sys 0m24.224s
Run Code Online (Sandbox Code Playgroud)
使用查找:
$ time find /tmp/many -type f -delete
real 0m9.978s
user 0m1.872s
sys 0m6.617s
Run Code Online (Sandbox Code Playgroud)
$ time ./dentls2 ./many
Total files: 10000000
real 10m25.749s
user 0m2.214s
sys 16m30.865s
Run Code Online (Sandbox Code Playgroud)
使用查找:
time find ./many -type f -delete
real 7m1.328s
user 0m9.209s
sys 4m42.000s
Run Code Online (Sandbox Code Playgroud)
所以看起来批处理系统调用并没有实时改进。新的工作dentls2花费更多的时间(四倍)只会导致更差的性能。因此,整体效率的净损失和更糟糕的延迟。dentls2更差。
造成这种情况的原因是因为 io_uring 产生内核调度程序线程来在内部执行取消链接工作,但是正在处理的目录 inode 一次只能由单个编写器修改。
基本上使用uring我们创建了许多小线程,但只允许从目录中删除一个线程。我们刚刚创建了一堆争用并消除了进行批量 IO 的优势。
使用 eBPF,您可以测量 unlink 频率并观察导致延迟的原因。
在 BTRFS 的情况下,它是内核函数调用btrfs_commit_inode_delayed_inode,它在unlink被调用时获取锁。
和 dentls2
# /usr/share/bcc/tools/funclatency btrfs_commit_inode_delayed_inode
Tracing 1 functions for "btrfs_commit_inode_delayed_inode"... Hit Ctrl-C to end.
nsecs : count distribution
0 -> 1 : 0 | |
2 -> 3 : 0 | |
4 -> 7 : 0 | |
8 -> 15 : 0 | |
16 -> 31 : 0 | |
32 -> 63 : 0 | |
64 -> 127 : 0 | |
128 -> 255 : 0 | |
256 -> 511 : 18 | |
512 -> 1023 : 120 | |
1024 -> 2047 : 50982 | |
2048 -> 4095 : 2569467 |******************** |
4096 -> 8191 : 4936402 |****************************************|
8192 -> 16383 : 1662380 |************* |
16384 -> 32767 : 656883 |***** |
32768 -> 65535 : 85409 | |
65536 -> 131071 : 21715 | |
131072 -> 262143 : 9719 | |
262144 -> 524287 : 5981 | |
524288 -> 1048575 : 857 | |
1048576 -> 2097151 : 293 | |
2097152 -> 4194303 : 220 | |
4194304 -> 8388607 : 255 | |
8388608 -> 16777215 : 153 | |
16777216 -> 33554431 : 56 | |
33554432 -> 67108863 : 6 | |
67108864 -> 134217727 : 1 | |
avg = 8533 nsecs, total: 85345432173 nsecs, count: 10000918
Run Code Online (Sandbox Code Playgroud)
使用find ... -delete:
# /usr/share/bcc/tools/funclatency btrfs_commit_inode_delayed_inode
Tracing 1 functions for "btrfs_commit_inode_delayed_inode"... Hit Ctrl-C to end.
nsecs : count distribution
0 -> 1 : 0 | |
2 -> 3 : 0 | |
4 -> 7 : 0 | |
8 -> 15 : 0 | |
16 -> 31 : 0 | |
32 -> 63 : 0 | |
64 -> 127 : 0 | |
128 -> 255 : 0 | |
256 -> 511 : 34 | |
512 -> 1023 : 95 | |
1024 -> 2047 : 1005784 |**** |
2048 -> 4095 : 8110338 |****************************************|
4096 -> 8191 : 672119 |*** |
8192 -> 16383 : 158329 | |
16384 -> 32767 : 42338 | |
32768 -> 65535 : 4667 | |
65536 -> 131071 : 3597 | |
131072 -> 262143 : 2860 | |
262144 -> 524287 : 216 | |
524288 -> 1048575 : 22 | |
1048576 -> 2097151 : 6 | |
2097152 -> 4194303 : 3 | |
4194304 -> 8388607 : 5 | |
8388608 -> 16777215 : 3 | |
avg = 3258 nsecs, total: 32585481993 nsecs, count: 10000416
Run Code Online (Sandbox Code Playgroud)
您可以从直方图中看到find平均花费 3258 纳秒,btrfs_commit_inode_delayed_inode但dentls2在函数中花费 8533 纳秒。
此外,直方图显示,整体 io_uring 线程花费至少两倍的时间等待锁定,大多数调用需要 4096-8091 纳秒,而大多数调用find需要 2048-4095 纳秒。
Find 是单线程的,不争用锁,而 `dentls2 是多线程的(由于 uring),它会产生锁争用,并且所经历的延迟反映在分析中。
总而言之,在现代系统上(在撰写本文时),您在软件中可以做的事情越来越少,以使其比预期的速度更快。
它曾经从磁盘读取一个大缓冲区,你可以将一个昂贵的 IO 调用复合到一个大的顺序读取中,而不是小型 getdents() 缓冲区通常最终可能成为的搜索 IO。
此外,由于其他改进,仅调用系统调用的开销较小,并且顺序/随机 IO 访问时间的重大改进消除了我们过去遇到的大 IO 瓶颈。
在我的系统上,此问题已成为内存/CPU 限制。在(至少)BTRFS 上存在一个单一访问器问题,这限制了您一次可以访问每个目录的单个 CPU/程序的取消链接的速度。即使在使用 tmpfs 的理想情况下,尝试对 IO 的产量进行批处理也有轻微的改进,并且在实际文件系统上通常更糟。
最重要的是,我们真的不再有这个问题了——1000 万个文件需要 4 小时才能删除的日子一去不复返了。
只需做一些简单的事情,例如find ... -delete. 与默认的简单设置相比,我尝试的任何优化似乎都无法产生值得编码(或分析)的重大性能改进。
虽然这个问题的一个主要原因是 ext3 对数百万个文件的性能,但这个问题的实际根本原因是不同的。
当需要列出目录时,在生成文件列表的目录上调用 readdir() 。readdir 是 posix 调用,但这里使用的真正 Linux 系统调用称为“getdents”。Getdents 通过用条目填充缓冲区来列出目录条目。
问题主要在于 readdir() 使用 32Kb 的固定缓冲区大小来获取文件。随着目录变得越来越大(大小随着文件的增加而增加)ext3 获取条目的速度越来越慢,额外的 readdir 的 32Kb 缓冲区大小仅足以包含目录中的一小部分条目。这会导致 readdir 一遍又一遍地循环并一遍又一遍地调用昂贵的系统调用。
例如,在我创建的包含超过 260 万个文件的测试目录中,运行“ls -1|wc-l”会显示许多 getdent 系统调用的大量 strace 输出。
$ strace ls -1 | wc -l
brk(0x4949000) = 0x4949000
getdents(3, /* 1025 entries */, 32768) = 32752
getdents(3, /* 1024 entries */, 32768) = 32752
getdents(3, /* 1025 entries */, 32768) = 32760
getdents(3, /* 1025 entries */, 32768) = 32768
brk(0) = 0x4949000
brk(0x496a000) = 0x496a000
getdents(3, /* 1024 entries */, 32768) = 32752
getdents(3, /* 1026 entries */, 32768) = 32760
...
Run Code Online (Sandbox Code Playgroud)
此外,在此目录中花费的时间很长。
$ time ls -1 | wc -l
2616044
real 0m20.609s
user 0m16.241s
sys 0m3.639s
Run Code Online (Sandbox Code Playgroud)
使此过程更有效的方法是使用更大的缓冲区手动调用 getdents。这显着提高了性能。
现在,您不应该自己手动调用 getdents,因此不存在正常使用它的接口(请查看手册页以查看 getdents!),但是您可以手动调用它并使您的系统调用调用方式更高效。
这大大减少了获取这些文件所需的时间。我写了一个程序来做到这一点。
/* I can be compiled with the command "gcc -o dentls dentls.c" */
#define _GNU_SOURCE
#include <dirent.h> /* Defines DT_* constants */
#include <err.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <unistd.h>
struct linux_dirent {
long d_ino;
off_t d_off;
unsigned short d_reclen;
char d_name[256];
char d_type;
};
static int delete = 0;
char *path = NULL;
static void parse_config(
int argc,
char **argv)
{
int option_idx = 0;
static struct option loptions[] = {
{ "delete", no_argument, &delete, 1 },
{ "help", no_argument, NULL, 'h' },
{ 0, 0, 0, 0 }
};
while (1) {
int c = getopt_long(argc, argv, "h", loptions, &option_idx);
if (c < 0)
break;
switch(c) {
case 0: {
break;
}
case 'h': {
printf("Usage: %s [--delete] DIRECTORY\n"
"List/Delete files in DIRECTORY.\n"
"Example %s --delete /var/spool/postfix/deferred\n",
argv[0], argv[0]);
exit(0);
break;
}
default:
break;
}
}
if (optind >= argc)
errx(EXIT_FAILURE, "Must supply a valid directory\n");
path = argv[optind];
}
int main(
int argc,
char** argv)
{
parse_config(argc, argv);
int totalfiles = 0;
int dirfd = -1;
int offset = 0;
int bufcount = 0;
void *buffer = NULL;
char *d_type;
struct linux_dirent *dent = NULL;
struct stat dstat;
/* Standard sanity checking stuff */
if (access(path, R_OK) < 0)
err(EXIT_FAILURE, "Could not access directory");
if (lstat(path, &dstat) < 0)
err(EXIT_FAILURE, "Unable to lstat path");
if (!S_ISDIR(dstat.st_mode))
errx(EXIT_FAILURE, "The path %s is not a directory.\n", path);
/* Allocate a buffer of equal size to the directory to store dents */
if ((buffer = calloc(dstat.st_size*3, 1)) == NULL)
err(EXIT_FAILURE, "Buffer allocation failure");
/* Open the directory */
if ((dirfd = open(path, O_RDONLY)) < 0)
err(EXIT_FAILURE, "Open error");
/* Switch directories */
fchdir(dirfd);
if (delete) {
printf("Deleting files in ");
for (int i=5; i > 0; i--) {
printf("%u. . . ", i);
fflush(stdout);
sleep(1);
}
printf("\n");
}
while (bufcount = syscall(SYS_getdents, dirfd, buffer, dstat.st_size*3)) {
offset = 0;
dent = buffer;
while (offset < bufcount) {
/* Don't print thisdir and parent dir */
if (!((strcmp(".",dent->d_name) == 0) || (strcmp("..",dent->d_name) == 0))) {
d_type = (char *)dent + dent->d_reclen-1;
/* Only print files */
if (*d_type == DT_REG) {
printf ("%s\n", dent->d_name);
if (delete) {
if (unlink(dent->d_name) < 0)
warn("Cannot delete file \"%s\"", dent->d_name);
}
totalfiles++;
}
}
offset += dent->d_reclen;
dent = buffer + offset;
}
}
fprintf(stderr, "Total files: %d\n", totalfiles);
close(dirfd);
free(buffer);
exit(0);
}
Run Code Online (Sandbox Code Playgroud)
虽然这并不能解决潜在的基本问题(大量文件,在一个文件系统中表现不佳)。它可能比发布的许多替代方案快得多。
作为一个预先考虑,应该删除受影响的目录并在之后重新制作它。由于目录的大小,目录只会增加大小并且即使里面有几个文件也可能保持性能不佳。
编辑:我已经清理了很多。添加了一个选项,允许您在运行时在命令行上删除并删除了一堆 treewalk 的东西,老实说,往后看充其量是有问题的。也被证明会产生内存损坏。
你现在可以做 dentls --delete /my/path
新结果。基于一个包含 182 万个文件的目录。
## Ideal ls Uncached
$ time ls -u1 data >/dev/null
real 0m44.948s
user 0m1.737s
sys 0m22.000s
## Ideal ls Cached
$ time ls -u1 data >/dev/null
real 0m46.012s
user 0m1.746s
sys 0m21.805s
### dentls uncached
$ time ./dentls data >/dev/null
Total files: 1819292
real 0m1.608s
user 0m0.059s
sys 0m0.791s
## dentls cached
$ time ./dentls data >/dev/null
Total files: 1819292
real 0m0.771s
user 0m0.057s
sys 0m0.711s
Run Code Online (Sandbox Code Playgroud)
有点惊讶这仍然如此有效!
Bre*_*bad 35
在data=writeback安装选项值得尝试,以防止文件系统的日志。这应该只在删除期间完成,但是如果在删除操作期间服务器正在关闭或重新启动,则存在风险。
根据这个页面,
一些应用程序在使用时表现出非常显着的速度提升。例如,当应用程序创建和删除大量小文件时,可以看到速度的提高 (...)。
该选项fstab在挂载操作中或挂载操作期间设置,替换data=ordered为data=writeback. 包含要删除的文件的文件系统必须重新挂载。
jft*_*uga 32
是否可以将此文件系统中的所有其他文件备份到临时存储位置,重新格式化分区,然后恢复文件?
Ale*_*rts 12
ext3 中没有每个目录文件的限制,只有文件系统 inode 限制(我认为子目录的数量是有限制的)。
删除文件后,您可能仍会遇到问题。
当一个目录有数百万个文件时,目录条目本身就变得非常大。每次删除操作都必须扫描目录条目,并且每个文件需要不同的时间,具体取决于其条目所在的位置。不幸的是,即使删除了所有文件,目录条目仍保留其大小。因此,即使目录现在是空的,需要扫描目录条目的进一步操作仍然需要很长时间。解决该问题的唯一方法是重命名目录,使用旧名称创建一个新目录,并将所有剩余文件转移到新目录。然后删除重命名的。
我没有对它进行基准测试,但这个人做了:
rsync -a --delete ./emptyDirectoty/ ./hugeDirectory/
Run Code Online (Sandbox Code Playgroud)
小智 7
TLDR:使用rsync -a --delete emptyfolder/ x.
这个问题有 5 万次浏览,还有不少答案,但似乎没有人对所有不同的回复进行基准测试。有一个指向外部基准的链接,但该链接已超过 7 年,并且没有查看此答案中提供的程序:https : //serverfault.com/a/328305/565293
这里的部分困难在于删除文件所需的时间在很大程度上取决于正在使用的磁盘和文件系统。就我而言,我使用在 Arch Linux 上运行 BTRFS 的消费者 SSD 进行了测试(更新至 2020-03),但我在不同的发行版(Ubuntu 18.04)、文件系统(ZFS)和驱动器上得到了相同的结果排序类型(RAID10 配置中的 HDD)。
每次运行的测试设置都相同:
# setup
mkdir test && cd test && mkdir empty
# create 800000 files in a folder called x
mkdir x && cd x
seq 800000 | xargs touch
cd ..
Run Code Online (Sandbox Code Playgroud)
检测结果:
rm -rf x: 30.43s
find x/ -type f -delete: 29.79
perl -e 'for(<*>){((stat)[9]<(unlink))}': 37.97s
rsync -a --delete empty/ x: 25.11s
(以下是此答案中的程序,但已修改为不打印任何内容或在删除文件之前等待。)
./dentls --delete x: 29.74
rsync每次我重复测试时,该版本都证明是赢家,尽管利润率很低。该perl命令比我系统上的任何其他选项都慢。
有点令人震惊的是,事实证明,这个问题的最佳答案中的程序在我的系统上并不比简单的rm -rf. 让我们深入研究为什么会这样。
首先,答案声称问题在于rm使用readdir32Kb 的固定缓冲区大小和getdents. 在我的 Ubuntu 18.04 系统上事实证明并非如此,它使用了四倍大的缓冲区。在 Arch Linux 系统上,它使用getdents64.
此外,答案误导性地提供了统计数据,给出了在大目录中列出文件的速度,但没有删除它们(这就是问题所在)。它比较dentls到ls -u1,但一个简单的strace显示,getdents是不是原因的原因ls -u1是缓慢的,至少不会在我的系统(Ubuntu的18.04与目录百万文件):
strace -c ls -u1 x >/dev/null
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
94.00 7.177356 7 1000000 lstat
5.96 0.454913 1857 245 getdents
[snip]
Run Code Online (Sandbox Code Playgroud)
此ls命令对 进行了一百万次调用lstat,这会减慢程序的运行速度。该getdents呼叫最多只能加0.455秒。getdents呼叫在dentls同一个文件夹中占用多长时间?
strace -c ./dentls x >/dev/null
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
99.91 0.489895 40825 12 getdents
[snip]
Run Code Online (Sandbox Code Playgroud)
这是正确的!尽管dentls只进行了 12 次调用而不是 245 次,但实际上系统运行这些调用需要更长的时间。因此,该答案中给出的解释实际上是不正确的 - 至少对于我已经能够对此进行测试的两个系统。
这同样适用于rm和dentls --delete。而rm调用 0.42 秒getdents,则dentls需要 0.53 秒。在任何情况下,绝大多数的时间都花在打电话unlink!
所以简而言之,不要期望看到大量的加速运行dentls,除非你的系统像作者的一样并且在单个getdents. 也许 glibc 的人在写出答案后的几年里已经大大加快了它的速度,现在需要线性的时间来响应不同的缓冲区大小。或者,响应时间可能getdents以某种不明显的方式取决于系统架构。