为什么打开文件比读取变量内容更快?

为什么打开文件比读取变量内容更快?

bash脚本中,我需要/proc/文件中的各种值。到目前为止,我有几十行直接 grep 文件,如下所示:

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo

为了提高效率,我将文件内容保存在变量中并 grep :

a=$(</proc/meminfo)
echo "$a" | grep -oP '^MemFree: *\K[0-9]+'

不需要多次打开文件,而应该只打开一次并 grep 变量内容,我认为这会更快 - 但实际上它更慢:

bash 4.4.19 $ time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null
real    0m0.803s
user    0m0.619s
sys     0m0.232s
bash 4.4.19 $ a=$(</proc/meminfo)
bash 4.4.19 $ time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null
real    0m1.182s
user    0m1.425s
sys     0m0.506s

dash对于和 也是如此zsh。我怀疑文件的特殊状态/proc/是一个原因,但是当我将 的内容复制/proc/meminfo到常规文件并使用时,结果是相同的:

bash 4.4.19 $ cat </proc/meminfo >meminfo
bash 4.4.19 $ time for i in $(seq 1 1000);do grep ^MemFree meminfo; done >/dev/null
real    0m0.790s
user    0m0.608s
sys     0m0.227s

使用此处的字符串来保存管道使其速度稍快一些,但仍然不如使用文件快:

bash 4.4.19 $ time for i in $(seq 1 1000);do <<<"$a" grep ^MemFree; done >/dev/null
real    0m0.977s
user    0m0.758s
sys     0m0.268s

为什么打开文件比从变量读取相同内容更快?

答案1

这里,不是关于打开文件相对读取变量的内容但更多的是关于是否分叉额外的进程。

grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo分叉一个执行grep打开的进程/proc/meminfo(内存中的虚拟文件,不涉及磁盘 I/O)读取它并匹配正则表达式。

其中最昂贵的部分是分叉进程并加载 grep 实用程序及其库依赖项、进行动态链接、打开语言环境数据库、磁盘上的数十个文件(但可能缓存在内存中)。

相比之下,关于读取的部分/proc/meminfo是微不足道的,内核需要很少的时间来生成其中的信息,并且grep需要很少的时间来读取它。

如果你运行它,你会发现用于读取的strace -c一个open()和一个系统调用与启动时执行的其他操作相比简直是微不足道(不计算分叉)。read()/proc/meminfogrepstrace -c

在:

a=$(</proc/meminfo)

在大多数支持 ksh 运算符的 shell 中$(<...),shell 只是打开文件并读取其内容(并删除尾随的换行符)。bash不同的是,效率低得多,因为它分叉一个进程来执行读取操作,并通过管道将数据传递给父进程。但在这里,它已经完成了一次,所以没关系。

在:

printf '%s\n' "$a" | grep '^MemFree'

外壳需要生成进程,它们同时运行,但通过管道相互交互。管道的创建、拆除以及写入和读取的成本很小。更大的成本是产生额外的进程。进程的调度也有一些影响。

您可能会发现使用 zsh<<<运算符会使速度稍微快一些:

grep '^MemFree' <<< "$a"

在 zsh 和 bash 中,这是通过将内容写入$a临时文件来完成的,这比生成额外的进程要便宜,但与直接获取数据相比,可能不会给您带来任何好处/proc/meminfo。这仍然比在磁盘上复制的方法效率低/proc/meminfo,因为临时文件的写入是在每次迭代时完成的。

dash不支持here-strings,但其heredocs是使用不涉及生成额外进程的管道实现的。在:

 grep '^MemFree' << EOF
 $a
 EOF

shell 创建一个管道,分叉一个进程。子进程grep将其 stdin 作为管道的读取端执行,而父进程在管道的另一端写入内容。

但管道处理和进程同步仍然可能比直接获取数据更昂贵/proc/meminfo

内容/proc/meminfo较短,制作时间并不长。如果您想节省一些 CPU 周期,您需要删除昂贵的部分:分叉进程和运行外部命令。

喜欢:

IFS= read -rd '' meminfo < /proc/meminfo
memfree=${meminfo#*MemFree:}
memfree=${memfree%%$'\n'*}
memfree=${memfree#"${memfree%%[! ]*}"}

避免bash其模式匹配效率很低的情况。使用zsh -o extendedglob,您可以将其缩短为:

memfree=${${"$(</proc/meminfo)"##*MemFree: #}%%$'\n'*}

请注意,这^在许多 shell 中都很特殊(Bourne、fish、rc、es 和 zsh 至少带有 Extendedglob 选项),我建议引用它。另请注意,echo不能用于输出任意数据(因此我使用了printf上面的方法)。

答案2

在第一种情况下,您只是使用 grep 实用程序并从 file 中查找内容/proc/meminfo, file/proc是一个虚拟文件系统,因此/proc/meminfofile 位于内存中,并且只需要很少的时间即可获取其内容。

但在第二种情况下,您要创建一个管道,然后使用该管道将第一个命令的输出传递到第二个命令,这是昂贵的。

区别在于/proc(因为它在内存中)和管道,请参见下面的示例:

time for i in {1..1000};do grep ^MemFree /proc/meminfo;done >/dev/null

real    0m0.914s
user    0m0.032s
sys     0m0.148s


cat /proc/meminfo > file
time for i in {1..1000};do grep ^MemFree file;done >/dev/null

real    0m0.938s
user    0m0.032s
sys     0m0.152s


time for i in {1..1000};do echo "$a"|grep ^MemFree; done >/dev/null

real    0m1.016s
user    0m0.040s
sys     0m0.232s

答案3

您正在呼叫外部的两种情况下的命令(grep)。外部调用需要子shell。分叉该 shell 是造成延迟的根本原因。两种情况相似,因此:相似的延迟。

如果您只想读取外部文件一次并多次使用它(从变量),请不要退出 shell:

meminfo=$(< /dev/meminfo)    
time for i in {1..1000};do 
    [[ $meminfo =~ MemFree:\ *([0-9]*)\ *.B ]] 
    printf '%s\n' "${BASH_REMATCH[1]}"
done

grep 调用只需要大约 0.1 秒,而不是整整 1 秒。

相关内容