由于页面缓存, os.walk() 在第一个 运行 之后快得多吗?
Is os.walk() much faster after the first run due to page caching?
我使用 os.walk
迭代,比如说,1000 个文件(只是迭代,没有对这些文件进行任何处理)。
第一个 运行 很慢,但随后的 运行 s(在同一路径上)大约快 20 倍。
据我所知,os.walk
和 os.listdir
(被 os.walk
使用)没有做任何缓存,FindFirstFile
/FindNextFile
(os.listdir
在我的 Windows 平台上使用)。
这是由于页面缓存还是其他原因造成的?
仅供参考,我正在尝试编写一个备份应用程序并且需要处理大量文件。如果确实是页面缓存的问题,那我就需要自己写一个缓存机制了。
您的 OS 在这里进行缓存;目录查找需要缓慢的磁盘访问,因此此类访问被大量缓存。
例如,ntfs.sys
驱动程序 uses the Data Map service 用于缓存目录列表等文件系统元数据。
我知道您已经找到了问题的答案,但这让我很好奇这些缓存在 Linux 中是如何工作的,所以我进行了一些基准测试。
我创建了一个包含1000个子目录的目录,在每个子目录中我创建了1000个文件。总共1000个文件夹和100万个文件。
seq 1000 | parallel "mkdir {} && seq 1000 | parallel touch {}/\{\}"
这应该会给 os.walk
一些有用的东西。接下来,我创建了一个测试脚本,它只计算目录中的文件数,递归使用 os.walk
:
def count_dir(path):
count = 0
for _, _, files in os.walk(path):
count += len(files)
return count
我 运行 这几次,最终平均执行时间为 2.27 秒。
接下来,我 运行 再次测试了几次,但在每次执行之间刷新了页面缓存,然后再次刷新了 dentry/inode 缓存,最后是同样的事情,但刷新了所有以上缓存。
To free pagecache:
echo 1 > /proc/sys/vm/drop_caches
To free dentries and inodes:
echo 2 > /proc/sys/vm/drop_caches
To free pagecache, dentries and inodes:
echo 3 > /proc/sys/vm/drop_caches
结果:
no flush page cache dentries/inodes all
mean 2.27 2.56 4.24 4.80
stdev 0.052 0.055 0.127 0.108
%RSD 2.27% 2.15% 2.99% 2.26%
median 2.26 2.54 4.21 4.78
iterations 27 27 31 10
基准测试是 运行 在新 SSD 驱动器上的 ext4
文件系统上。 Linux 版本是 3.17.7.
毫不奇怪,页面缓存和 dentries/inodes 缓存对这个基准测试的结果影响很大。然而,我有点惊讶,仅刷新页面缓存会产生如此大的差异,因为应该可以一次读取这些文件的大部分文件系统元数据,但我可能遗漏了一些东西。
我会说页面缓存并没有太大的不同,因为包含文件系统元数据的页面的缓存未命中率相对较低。但我的直觉似乎是错误的。有什么想法吗?
我使用 os.walk
迭代,比如说,1000 个文件(只是迭代,没有对这些文件进行任何处理)。
第一个 运行 很慢,但随后的 运行 s(在同一路径上)大约快 20 倍。
据我所知,os.walk
和 os.listdir
(被 os.walk
使用)没有做任何缓存,FindFirstFile
/FindNextFile
(os.listdir
在我的 Windows 平台上使用)。
这是由于页面缓存还是其他原因造成的?
仅供参考,我正在尝试编写一个备份应用程序并且需要处理大量文件。如果确实是页面缓存的问题,那我就需要自己写一个缓存机制了。
您的 OS 在这里进行缓存;目录查找需要缓慢的磁盘访问,因此此类访问被大量缓存。
例如,ntfs.sys
驱动程序 uses the Data Map service 用于缓存目录列表等文件系统元数据。
我知道您已经找到了问题的答案,但这让我很好奇这些缓存在 Linux 中是如何工作的,所以我进行了一些基准测试。
我创建了一个包含1000个子目录的目录,在每个子目录中我创建了1000个文件。总共1000个文件夹和100万个文件。
seq 1000 | parallel "mkdir {} && seq 1000 | parallel touch {}/\{\}"
这应该会给 os.walk
一些有用的东西。接下来,我创建了一个测试脚本,它只计算目录中的文件数,递归使用 os.walk
:
def count_dir(path):
count = 0
for _, _, files in os.walk(path):
count += len(files)
return count
我 运行 这几次,最终平均执行时间为 2.27 秒。
接下来,我 运行 再次测试了几次,但在每次执行之间刷新了页面缓存,然后再次刷新了 dentry/inode 缓存,最后是同样的事情,但刷新了所有以上缓存。
To free pagecache:
echo 1 > /proc/sys/vm/drop_caches
To free dentries and inodes:
echo 2 > /proc/sys/vm/drop_caches
To free pagecache, dentries and inodes:
echo 3 > /proc/sys/vm/drop_caches
结果:
no flush page cache dentries/inodes all
mean 2.27 2.56 4.24 4.80
stdev 0.052 0.055 0.127 0.108
%RSD 2.27% 2.15% 2.99% 2.26%
median 2.26 2.54 4.21 4.78
iterations 27 27 31 10
基准测试是 运行 在新 SSD 驱动器上的 ext4
文件系统上。 Linux 版本是 3.17.7.
毫不奇怪,页面缓存和 dentries/inodes 缓存对这个基准测试的结果影响很大。然而,我有点惊讶,仅刷新页面缓存会产生如此大的差异,因为应该可以一次读取这些文件的大部分文件系统元数据,但我可能遗漏了一些东西。
我会说页面缓存并没有太大的不同,因为包含文件系统元数据的页面的缓存未命中率相对较低。但我的直觉似乎是错误的。有什么想法吗?