[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
返回列表 发帖

[系统相关] 讨论:批处理中for /f 解析命令输出的效率

讨论:for + 命令 的效率
by 随风 @bbs.bathome.net @2009-03-03
前不久的一个关于数字排序的帖子中曾提到过一个没太引人注意的问题。
1、for /f 的集中 即:in 后面的括弧中 使用命令会不会影响代码的运行效率。
   如果会,到底有多大的影响,依据是什么。
2、findstr 命令,真的很影响效率吗,它在速度方面的优、缺点都是怎样的。
正好这几天有空,专门对这几个问题作了些测试,拿出来讨论一下。
在batman的数字排序帖中,我曾说for的in 中使用任何命令都会影响效率,现证实如下:
测试环境:xp-sp2 cup 1.00GHz  内存:128MB
测试环境极为垃圾,各位若要测试,可能需要将样本文件体积放大,才能看出时间差别。
测试最终得到的结果不一定精确,但应该足以说明一些问题。
以下所有测试都需 time0.bat 的配合
.
time0.bat 内容
  1. :time0  计算时间差 (封装)
  2. @echo off&setlocal&set /a n=0&rem code 随风 @bbs.bathome.net
  3. for /f "tokens=1-8 delims=.: " %%a in ("%~1:%~2") do (
  4. set /a n+=10%%a%%100*360000+10%%b%%100*6000+10%%c%%100*100+10%%d%%100
  5. set /a n-=10%%e%%100*360000+10%%f%%100*6000+10%%g%%100*100+10%%h%%100)
  6. set /a s=n/360000,n=n%%360000,f=n/6000,n=n%%6000,m=n/100,n=n%%100
  7. set "ok=%s% 小时 %f% 分钟 %m% 秒 %n% 毫秒"
  8. endlocal&set %~3=%ok:-=%&goto :EOF
复制代码

测试一、
先用一个公认不耗时的内部命令(set 命令)来测试。
代码流程:
   先设置10个以点为开头的变量,再运行10次 for /f + set . 来显示,总共显示 100个结果。
   与 for 直接显示 1000 个结果来对比。
  1. @echo off&setlocal enabledelayedexpansion
  2. set "str="
  3. for /f "delims=" %%a in ('set . 2^>nul') do set "%%a="
  4. for /l %%a in (1 1 10) do set .%%a=%%a&set str=!str! %%a
  5. set t=%time%
  6. for /l %%j in (1 1 100) do (
  7.   for /f "delims=" %%a in ("%str%") do (
  8.     for %%i in (%%a) do echo %%i >nul
  9. ))
  10. call time0 %t% %time% ok
  11. echo  for /f 命令 直接显示1000个结果 %ok%
  12. echo.
  13. for /l %%a in (1 1 10) do call :loop %%a
  14. pause
  15. :loop
  16. set t=%time%
  17. for /f "delims=" %%a in ('set .') do echo %%a >nul
  18. call time0 %t% %time% ok
  19. echo 运行%1次 %ok%
  20. goto :EOF
复制代码

测试一 结果:
for /f 命令 直接显示1000个结果 0 小时 0 分钟 0 秒 12 毫秒
运行1次 0 小时 0 分钟 0 秒 6 毫秒
运行2次 0 小时 0 分钟 0 秒 7 毫秒
运行3次 0 小时 0 分钟 0 秒 6 毫秒
运行4次 0 小时 0 分钟 0 秒 7 毫秒
运行5次 0 小时 0 分钟 0 秒 6 毫秒
运行6次 0 小时 0 分钟 0 秒 7 毫秒
运行7次 0 小时 0 分钟 0 秒 6 毫秒
运行8次 0 小时 0 分钟 0 秒 7 毫秒
运行9次 0 小时 0 分钟 0 秒 7 毫秒
运行10次 0 小时 0 分钟 0 秒 6 毫秒
.
可以看出 直接用 for 显示 1000 个结果耗时只需 12 毫秒
而用 for /f + set . 总共显示100个结果平均每次耗时最少6毫秒,
如果显示1000个结果那么效率可想而知。
难道启动 set 命令这么耗时吗?很简单作一次测试便知:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /l %%a in (1 1 10) do set .%%a=%%a
  3. set t=%time%
  4. for /l %%a in (1 1 100) do set . >nul&echo a>nul
  5. call time0 %t% %time% ok
  6. echo  %ok%
  7. pause
复制代码

耗时:0 小时 0 分钟 0 秒 3 毫秒
结果显示启动100次耗时才 3 毫秒,那为何上面的耗时这么多呢?继续看下面的测试。
======================================================================
记得我曾经发过一个关于代码运行效率的帖,里面说批处理中频繁使用findstr命令
会大大影响代码效率,现测试该说法并不完全正确。
测试二、
   测试每启动一次 findstr 命令需要多少时间。
   测试样本文件 a.txt 和 c.txt
   其中:
   a.txt 有5000行 大小:290KB
   c.txt 一行 大小:1KB
  1. @echo off
  2. echo a>c.txt
  3. set t=%time%
  4. findstr .* c.txt>nul
  5. call time0 %t% %time% ok
  6. echo %Ok%
  7. set t=%time%
  8. findstr .* a.txt>nul
  9. call time0 %t% %time% ok
  10. echo %Ok%
  11. set t=%time%
  12. for /l %%a in (1 1 100) do findstr .* c.txt>nul
  13. call time0 %t% %time% ok
  14. echo %Ok%
  15. set t=%time%
  16. for /l %%a in (1 1 100) do findstr .* a.txt>nul
  17. call time0 %t% %time% ok
  18. echo %Ok%
  19. pause
复制代码

测试二 结果
启动一次,遍历 1kb 的文件 0 小时 0 分钟 0 秒 7 毫秒
启动一次,遍历 290kb 的文件 0 小时 0 分钟 0 秒 8 毫秒
启动100次,遍历 1kb 的文件 0 小时 0 分钟 7 秒 33 毫秒
启动100次,遍历 290kb 的文件 0 小时 0 分钟 7 秒 42 毫秒
.
可以看出,findstr 命令无论是遍历大文件,还是小文件,耗时是差不多的
即使是启动100次,平均耗时也与启动一次差不多,
因此得出findstr的启动耗时应该是 7 毫秒左右。(经测试:find命令也是一样)
得此结果,便可根据实际情况来决定是否该在for中来使用findstr命令了。
也可证明findstr 遍历文件的速度是很快的,且不受文件大小影响。
事实上 findstr 写入文件的速度也是快的惊人,最后有作测试。
======================================================================
为何 for /f + 命令 有时会使效率大大降低。
测试三、
  1. @echo off&setlocal enabledelayedexpansion
  2. cd.>c.txt
  3. for /l %%a in (1 1 5000) do (
  4.    echo bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb>>c.txt
  5. )
  6. call :jjj 1
  7. call :loop 1 a&echo.
  8. call :jjj 1
  9. call :loop 2 a&echo.
  10. call :jjj 98
  11. call :loop 100 a&echo.
  12. call :loop 5000 b&echo.
  13. pause
  14. :loop
  15. set t=%time%
  16. for /f "delims=" %%a in ('findstr "%2" c.txt') do set a=a
  17. call time0 %t% %time% ok
  18. echo 匹配结果为%1行 %ok%
  19. goto :EOF
  20. :jjj
  21. for /l %%a in (1 1 %~1) do echo a>>c.txt
  22. goto :EOF
复制代码

测试三 结果:
findstr匹配结果为1行时耗时: 0 小时 0 分钟 0 秒 15 毫秒
findstr匹配结果为2行时耗时: 0 小时 0 分钟 0 秒 14 毫秒
findstr匹配结果为100行时耗时: 0 小时 0 分钟 0 秒 14 毫秒
findstr匹配结果为5000行时耗时:0 小时 0 分钟 2 秒 19 毫秒
.
可以看出 for /f + findstr 命令
当 findstr 匹配的结果为 1-100 行时,耗时是差不多的。
但当匹配结果有 5000 行时,效率明显降低。
再结合第二个测试结果,每启动一次 findstr 命令都耗时 7 毫秒。
再用这里的 15-7=8 也就是说多用了 8 毫秒
那这 8 毫秒是哪里耗去了呢?
我认为:
这是管道消耗的时间,for /f + 命令  命令所得到的结果是通过管道传递给for的,
而管道的耗时 大约是在 6-8 毫秒之间,这就能解释为什么 for + set 要耗时 6 毫秒
for + findstr 要耗时 15 毫秒(直接启动findstr命令耗时 7 毫秒)
但为什么匹配结果为5000行时,同样是启动一次findstr 而时间却要如此之多呢?
还是管道,管道就向一辆汽车,最多载100人,如果超出100,哪怕只超出1人,
那么这辆汽车也要多开一次,耗时也就多一次。但即使是只有1人,它也必须开一次。
测试管道命令耗时代码
  1. @echo off
  2. set t=%time%
  3. echo a|findstr .*>nul
  4. call time0 %t% %time% ok
  5. echo  %ok%
  6. pause
复制代码

耗时:0 小时 0 分钟 0 秒 15 毫秒
正好等于 for + findstr 的耗时结果
最后总结:
“for + 命令” 运行效率等于 for耗时+命令耗时+管道耗时(数据多时,管道耗时同时增加)
效率总结:
代码中过多使用 for + 命令 会在一定程度上影响效率,每次最少消耗 6 毫秒左右。
findstr 命令每启动一次至少消耗 7 毫秒左右。
另外值得一提的是:findstr 命令有众多开关,
当使用某些开关时,findstr的运行效率会受文件大小的影响,比如: /in /ix
.
最后再作几个写入文件速度的测试
1、for /f 直接循环
2、for /f + 改变句柄指向
3、findstr写入文件
样本文件c.txt 为5000行,大小:281KB
  1. @echo off&setlocal enabledelayedexpansion
  2. set t=%time%
  3. cd.>d.txt
  4. for /f "delims=" %%a in (c.txt) do >>d.txt echo %%a
  5. call time0 %t% %time% ok
  6. echo  %ok%
  7. set t=%time%
  8. echo.>nul 3>d.txt
  9. for /f "delims=" %%a in (c.txt) do echo %%a
  10. echo.>nul 4>con
  11. call time0 %t% %time% ok
  12. echo  %ok%
  13. set t=%time%
  14. findstr .* c.txt>e.txt
  15. call time0 %t% %time% ok
  16. echo  %ok%
  17. pause
复制代码

for /f  耗时: 0 小时 0 分钟 1 秒 47 毫秒
for+改变句柄指向 耗时: 0 小时 0 分钟 0 秒 28 毫秒
findstr 耗时: 0 小时 0 分钟 0 秒 7 毫秒
得出结论:
当需要定向众多内容到文本时
若能用findstr命令完成时,findstr当为首选。
其次“改变句柄指向”效率也是可观的,比直接用 >> 要快5倍左右。
技术问题请到论坛发帖求助!

回复 5楼 的帖子

有一点疑问,如果是抓入内存,那么应该不会与内容多少有关吧?而应该是与内存的大小有关了,比如列子中,匹配5000行就明显慢一些,按你的说法,应该也是只启动一次cmd 啊。
技术问题请到论坛发帖求助!

TOP

返回列表