在bash
脚本中,我需要/proc/
文件中的各种值。到目前为止,我有几十行直接像这样对文件进行 grepping:
grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo
为了提高效率,我将文件内容保存在一个变量中并grepped:
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
使用 here 字符串来保存管道会稍微快一些,但仍然不如文件快:
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
为什么打开文件比从变量中读取相同内容更快?
在这里,这不是关于打开文件与读取变量的内容,而是更多关于是否分叉额外的进程。
grep -oP '^MemFree: *\K[0-9]+' /proc/meminfo
派生一个执行grep
打开的进程/proc/meminfo
(一个虚拟文件,在内存中,不涉及磁盘 I/O)读取它并匹配正则表达式。其中最昂贵的部分是分叉进程并加载 grep 实用程序及其库依赖项,进行动态链接,打开语言环境数据库,磁盘上的数十个文件(但可能缓存在内存中)。
相比之下,关于读取的部分
/proc/meminfo
微不足道,内核需要很少的时间来生成其中的信息,并且grep
需要很少的时间来读取它。如果你在上面运行
strace -c
,你会看到用于读取的一个open()
和一个系统调用与启动其他所有操作相比是花生(不计算分叉)。read()
/proc/meminfo
grep
strace -c
在:
在大多数支持该
$(<...)
ksh 运算符的 shell 中,shell 只是打开文件并读取其内容(并去除尾随的换行符)。bash
不同并且效率低得多,因为它分叉一个进程来进行读取并通过管道将数据传递给父进程。但是在这里,它已经完成了一次,所以没关系。在:
shell 需要生成两个进程,它们同时运行,但通过管道相互交互。管道的创建、拆除以及从中写入和读取的成本很少。更大的成本是产生额外的进程。进程的调度也有一些影响。
您可能会发现使用 zsh
<<<
运算符会稍微快一些:在 zsh 和 bash 中,这是通过将 的内容写入
$a
临时文件来完成的,这比产生额外进程的成本更低,但与直接获取数据相比可能不会给您带来任何收益/proc/meminfo
。这仍然比/proc/meminfo
在磁盘上复制的方法效率低,因为临时文件的写入是在每次迭代时完成的。dash
不支持 here-strings,但它的 heredocs 是使用不涉及产生额外进程的管道实现的。在:外壳创建一个管道,分叉一个进程。子进程
grep
以其标准输入作为管道的读取端执行,父进程将内容写入管道的另一端。但是,管道处理和进程同步仍然可能比直接获取数据更昂贵
/proc/meminfo
。内容
/proc/meminfo
很短,制作时间也不长。如果你想节省一些 CPU 周期,你想删除昂贵的部分:分叉进程和运行外部命令。喜欢:
避免
bash
其模式匹配非常低效。使用zsh -o extendedglob
,您可以将其缩短为:请注意,这
^
在许多 shell 中是特殊的(Bourne、fish、rc、es 和 zsh,至少带有扩展的 glob 选项),我建议引用它。另请注意,echo
不能用于输出任意数据(因此我在printf
上面使用)。在您的第一种情况下,您只是使用 grep 实用程序并从 file 中查找某些内容
/proc/meminfo
,这/proc
是一个虚拟文件系统,因此/proc/meminfo
file 位于内存中,并且只需要很少的时间即可获取其内容。但是在第二种情况下,您正在创建一个管道,然后使用该管道将第一个命令的输出传递给第二个命令,这很昂贵。
区别在于
/proc
(因为它在内存中)和管道,请参见下面的示例:在这两种情况下,您都在调用外部命令(grep)。外部调用需要一个子shell。分叉该外壳是延迟的根本原因。这两种情况是相似的,因此:相似的延迟。
如果您只想读取外部文件一次并多次使用它(从变量中),请不要离开外壳:
grep 调用只需要大约 0.1 秒而不是 1 秒。