【问题标题】:Why is a 'for' loop faster than a 'while' loop for reading from a file?为什么从文件中读取的“for”循环比“while”循环快?
【发布时间】:2013-09-21 08:05:39
【问题描述】:

前言:

我一直被告知,在 shell 中工作时,最好在 for 循环上执行 while 循环,并且您不应该使用带有命令替换的 for 循环 cat'ing a文件。我的理解是,造成这种情况的原因有很多,包括:

  • for 循环需要一次将所有要处理的数据加载到内存中
  • for 循环默认在空格而不是换行符上进行分词,因此除了必须将所有 in 文件保存在内存中之外,您还有更多的分词会占用内存
  • for 循环不会在“右侧”开始处理,直到您的 in 语句中的所有内容都完成加载,这意味着您在等待结果的部分时间里,实际上什么都没有发生。重新“预加载”。

然而,在做一些简单的测试时,我发现虽然for 循环中的内存消耗似乎更大(正如预期的那样),但while 循环的实际性能更低。这并不是一个巨大的差异,而且在任何现代机器上都可能开始产生影响的规模上,我可能会切换到 awk 或 python,但我仍然很好奇为什么会发生这种情况。

测试设置:

我做了一系列简单的测试,只是将文件的行回显到 /dev/null。我的输入是两个分别包含 100K 和 1Mil IP 地址的平面文件。在我下面的输出中是一个测试,但我运行了几次,每次都得到类似的结果。我在 2013 MBA(i7,8g Mem)上运行这个测试。

测试结果

Ds-MacBook-Air:~ d$ time for i in $(cat /tmp/ips.100k);do echo $i > /dev/null;done

real    0m1.629s
user    0m1.154s
sys 0m0.480s
Ds-MacBook-Air:~ d$ time for i in $(cat /tmp/ips.mill);do echo $i > /dev/null;done

real    0m17.567s
user    0m12.414s
sys 0m5.131s
Ds-MacBook-Air:~ d$ time while read i;do echo $i > /dev/null;done < /tmp/ips.100k

real    0m2.148s
user    0m1.493s
sys 0m0.655s
Ds-MacBook-Air:~ d$ time while read i;do echo $i > /dev/null;done < /tmp/ips.mill

real    0m21.536s
user    0m14.915s
sys 0m6.617s

Ds-MacBook-Air:~ d$ tail -5 /tmp/ips.100k /tmp/ips.mill
==> /tmp/ips.100k <==
1.1.134.155
1.1.134.156
1.1.134.157
1.1.134.158
1.1.134.159

==> /tmp/ips.mill <==
1.15.66.59
1.15.66.60
1.15.66.61
1.15.66.62
1.15.66.63

Ds-MacBook-Air:~ d$ wc -l /tmp/ips.100k /tmp/ips.mill
  100000 /tmp/ips.100k
 1000000 /tmp/ips.mill
 1100000 total

关于for 循环与while 循环的断言,我没有任何直接引用,但我在 ~~TLDP~~ Wooldridge 文档或其他 Bash 编程指南中特别提到了它(一些快速的谷歌搜索并不能得到我几年前大部分时间阅读的确切位置。)

【问题讨论】:

  • $(&lt;testfile)$(cat testfile) 的更有效替代品,如果您只针对 bash。
  • ...顺便说一下,在 freenode 的 #bash 中,我们倾向于警告人们不要使用来自 TLDP 的文档,尤其是 ABS ——虽然它并不经常完全错误,它经常展示导致错误的不良做法。 mywiki.wooledge.org/BashGuide 是更精心策划和积极维护的文档。
  • 如果你重构重定向到...; done &gt;/dev/null 有关系吗?
  • @tripleee 我希望这可以全面提高性能,但不会更改 for-vs-while 增量(假设对 for 循环进行相同的转换)。

标签: bash performance loops for-loop while-loop


【解决方案1】:

这里的区别在于,在$(cat testfile) 的情况下,您将整个测试文件一次读入内存并对其进行字符串拆分,而在while read 的情况下,您一次读取一行。

理所当然,少量的大读取效率更高。

$(cat testfile) 方法也引入了一些错误,其中字符串拆分(您知道)和 glob 扩展(您可能不知道)文件内容 - 也就是说,如果您有*,可以替换为当前目录下的文件列表。

【讨论】:

  • 对我来说似乎磁盘操作的数量在这里并不那么重要。如果是,echo "$(&lt; file)" | while read i 应该接近于for i in $(&lt; file)。但是,我只得到了非常轻微的改进。虽然read是内置的,但我猜它的重复执行会减慢循环速度。
  • @Socowi,更重要的是read 一次读取一个字节,因此无论是从 FIFO 还是文件读取,都会有很多系统调用。 (echo "$(&lt;file)" | 方法仍在执行所有这些系统调用,只是从运行echo 的 bash 子shell 中的 FIFO 读取...但您仍然需要支付上下文切换惩罚)。
猜你喜欢
  • 2021-10-01
  • 2017-11-29
  • 2010-11-11
  • 2011-01-29
  • 1970-01-01
  • 2019-07-06
  • 1970-01-01
  • 1970-01-01
  • 2023-04-09
相关资源
最近更新 更多