Board logo

标题: [转贴] 浅谈提高批处理代码效率的编写习惯 [打印本页]

作者: HAT    时间: 2011-9-6 10:27     标题: 浅谈提高批处理代码效率的编写习惯

一,慎用外部命令


1、能用内部命令就用尽量内部命令
  1. more 1.txt
复制代码
  1. type 1.txt
复制代码
rem 后者一定比前者快,因为甭管外部命令运行时效率多高,它们启动慢的缺陷就导致了不适宜大量使用,当然,碰到“专业对口”或者要处理大文件的情况,还是可以请外部命令出山的,不过一定切忌滥用。


2,尤其要避免在循环中频繁执行外部命令
  1. for %%a in (*.txt) do (
  2.     findstr test "%%a"&&echo 在 "%%a" 中找到关键词
  3. )
复制代码
  1. for /f "delims=" %%a in ('findstr /m test *.txt') do (
  2.     echo 在 "%%a" 中找到关键词
  3. )
复制代码
rem 实现同样的功能,前者的设计却是非常不合理的,在循环体重频繁调用外部命令将极大地影响脚本效率,也十分消耗系统资源。所以,非要用外部命令的话一定要尽量在外层循环中使用,以降低调用次数。


3,避免频繁从命令中获取输入
  1. for /f "delims=" %%a in ('type 1.txt') do echo %%a
复制代码
  1. for /f "delims=" %%a in (1.txt) do echo %%a
复制代码
rem 前者看似只是多执行了一个 type,for /f 从命令获取输入的本质是以 cmd /c 开启新的 cmd 进程来执行命令,for 命令再获取其输出作为输入,所以一旦直接从命令中获取输入,等于运行了一次 cmd /c,也就是外部命令。


4、好钢用在刀刃上
  1. for /f "delims=" in (大文件.txt) do set /a "n+=1"&echo !n!:%%a
复制代码
  1. findstr . 大文件.txt|findstr /n .*
复制代码
rem 碰到大文件时,与大量的内部命令相比,一两个外部命令的启动耗时将不足为道,这时用外部命令效率反而更高
作者: HAT    时间: 2011-9-6 10:27

二,选择合理的循环方式


1、少用 call 和 goto
  1. :a
  2. set /a n+=1
  3. echo %n%
  4. if %n% lss 100 goto a
  5. pause
复制代码
  1. for /l %%a in (1 1 100) do echo %%a
  2. pause
复制代码
rem for 的效率远超 call 与 goto,所以尽量用 for 作为循环结构的主体,尤其要注意的是,%0 这种循环方式其实是执行 cmd /c,cmd 是外部命令,所以这也是很低效的


2、大循环中避免使用 goto
  1. for /l %%a in (1 1 1000000) do if %%a==1 goto next
  2. :next
  3. pause
复制代码
  1. for /l %%a in (1 1 1000000) do if %%a==1 call :next
  2. :next
  3. pause
复制代码
rem for 迭代无法打断,goto 必须等待所有迭代的预处理都执行完毕才能跳转,而 call 却是立刻跳转,不过执行完了还得回来,并且存在 call 的次数上限,所以用哪个应该视情况而定
作者: HAT    时间: 2011-9-6 10:27

三,合理使用临时文件


1,从命令获取大量输入时,尽量使用临时文件
  1. for /f "delims=" %%a in ('dir /s /b \') do echo %%a
复制代码
  1. dir /s /b \>tmp.txt
  2. for /f "delims=" %%a in (tmp.txt) do echo %%a
复制代码
rem 因为前者是把“临时文件”放在内存中的,过量的内存占用将明显影响 cmd 的执行效率,后者却不用担心这一点。


2,前后存在内部命令或者语块时慎用管道
  1. (for /l %%a in (1 1 1000) do echo %%a)|sort
复制代码
  1. (for /l %%a in (1 1 1000) do echo %%a)>tmp.txt
  2. sort tmp.txt
复制代码
rem 前者的管道符前后存在语块,将开启新的 cmd 进程来执行此处的 for /l,所以降低了效率,此时改用临时文件就快了许多
作者: HAT    时间: 2011-9-6 10:28

四,for 嵌套中,越稳定的循环应该越少参与递归


1,参数固定的 for 应位于最外层
  1. for /l %%a in (1 1 1000) do (
  2.     for /f "tokens=2" %%b in ("abc test 123") do echo %%a%%b
  3. )
复制代码
  1. for /f "tokens=2" %%a in ("abc test 123") do (
  2.     for /l %%b in (1 1 1000) do echo %%a%%b
  3. )
复制代码
rem 同样的代码仅仅是次序不同,实现同样的效果的用时却是后者更快,因为那句 for /f 的结果是恒定的,所以前者产生了 999 次多余的循环


2,越小的循环越靠外
  1. for /l %%a in (1 1 1000) do (
  2.     for /l %%b in (1 1 10) do title %%a%%b
  3. )
复制代码
  1. for /l %%a in (1 1 10) do (
  2.     for /l %%b in (1 1 1000) do title %%a%%b
  3. )
复制代码
rem 这两段中的 for 循环的参数同样都是“不稳定的”,但是后者就是比前者快上一筹


3,越核心的递归层,命令最好越少
  1. for /l %%a in (1 1 100) do (
  2.     for /l %%b in (1 1 100) do (
  3.         set /a n=%%b*2
  4.         echo %%a,!n!
  5.     )
  6. )
复制代码
  1. for /l %%a in (1 1 100) do (
  2.     set /a n=%%a*2
  3.     for /l %%b in (1 1 100) do echo !n!,%%b
  4. )
复制代码
rem 前后代码的输出量是相同的,但是因为后者内层的循环中命令很少,所以效率就高了很多
作者: HAT    时间: 2011-9-6 10:29

五,操作尽量少


1、命令能合并就合并
  1. set m=%m: =%
  2. set m=@%m%@
复制代码

  1. set m=@%m: =%@
复制代码

rem 命令少一条是一条,这对效率的提升也是很直观的


2、充分利用命令的特性
  1. (for %%a in (*.txt) do type %%a)>out.txt
复制代码

  1. type *.txt>out.txt
复制代码

rem 很多命令本身就具有批量操作的能力,或者它们的某些特性可以被另类地利用,所以平时应该多挖掘命令的潜力


3、重定向的次数尽量少
  1. for /l %%a in (1 1 1000) do echo>>1.txt %%a
复制代码

  1. (for /l %%a in (1 1 1000) do echo %%a)>>1.txt
复制代码

rem 重定向也是消耗时间的,所以能一次性搞定最好

4、变量尽量少
  1. @echo off&setlocal enabledelayedexpansion
  2. for /f "tokens=1* delims=:" %%a in ('findstr /n .* 大文件.txt') do (
  3. set n=%%a
  4. set @!n!=%%b
  5. )
  6. (for /l %%a in (!n! -1 1) do echo;!@%%a!)>倒文.txt
  7. pause
复制代码

  1. @echo off&setlocal enabledelayedexpansion
  2. (for /f "tokens=1* delims=:" %%a in ('findstr /n .* 大文件.txt') do (
  3. set n=0000%%a
  4. echo !n:~-5! %%b
  5. ))>tmp.txt
  6. (for /f "tokens=1*" %%a in ('sort /r tmp.txt') do echo;%%b)>倒文.txt
  7. pause
复制代码

rem 以上两条均为从文件中倒序输出文本的代码,但是后者在处理大文件时要快上很多,因为 cmd 是以变量表的方式有序保存变量的,所以变量多时,存取变量有明显的延时,换句话说,变量越多,运行越慢。


转自:http://tieba.baidu.com/p/1187281687
作者: wc726842270    时间: 2011-9-6 18:39

看了老大的(一,3)才明白这句话——for本身是一个特殊的命令,类似于一个特化的命令解释器,因为它的功能实现需要执行多条语句,因此它必须也具有对命令行(特指do后的命令行)分析处理的功能(这个好像是出自JM所写的”逃逸字符%的详细解释“)
作者: cjiabing    时间: 2011-9-6 20:43

既然从贴吧转发过来了,我也把我的评论转发过来:
我想谈谈Goto并纠正一些错误的观点。
作者所谈及的“少用 call 和 goto”,call我就不说了,在必要的时候还得用,不必要的地方影响效率。至于goto,我认为楼主没有在标题注明在什么情况下少用goto,以及内容中没有解释清楚为什么少用goto。最主要的一个失误就是,“少用 call 和 goto”这个标题误导了许多人,使得人们以为goto是多么的不好,能不用尽量不用。
如楼主提供的例子,例子中用goto肯定不如用for。但这只是一个方面。像“set /p var=&if %var%==yes goto end”这种没有goto不方便,用FOR多此一举。因此,楼主仅仅以FOR代替GOTO这单方面的原因来否定GOTO,以此强调少用GOTO是有欠严谨的,有点以偏概全。
楼主虽然用了“少用”二字,但从影响结果而言,以及从其他地方了解的情况来看,这个“少用”二字并未能消除人们对GOTO命令的偏见。内容比较具体,但标题有点夸大了。因此,我希望楼主在讲解这方面的内容时,能够从GOTO本身意义和效率来谈,解释清楚是何种条件下“少用 call 和 goto”,而不能胡乱一句话就搪塞完了。



2011-09-03 01:42 回复  

cjiabing
10位粉丝 24楼

另外再谈谈Call与Goto的区别。
两者都可以跳转,但Call功能更多,而Goto比较单调。最明显的区别在于,Call有去有回,而Goto有去无回。具体原因我不是很明了,但Call在执行跳出去的时候,已经做了一个标记,用于遇到goto :eof时找到返回的位置。因此,Call在时间(命令执行先后顺序)上多了一个操作,这个操作在多层Call中是很明显的,Call从原位置跳出去了,他仍然时刻想着跳回去。这个原理使得Call可能占据了更多空间环境。而Goto则比较简单,他跳出去了就不用考虑往回跳,省去了这个标记和返回的过程。
一直想谈谈如何中止FOR的循环,楼主例二提供了两个很好的例子。FOR+Call,因为CALL的特性,使得可以确保FOR进程的前提下,获得对FOR进程的更好的干预。FOR+Goto,因为Goto不具有CALL的特性,GOTO无法嵌到FOR当中,因此,只能等FOR预处理完了才能GOTO。
在FOR中,CALL可以跳出FOR循环外,并能够在遇到GOTO :EOF时返回FOR中,继续FOR的进程。但GOTO只能跳出循环外,而不能返回FOR当中。
受两者命令自身特性的影响,它们在FOR中有着各自不同的用法。GOTO必须等FOR预处理完才能执行,而CALL可以在预处理的同时执行,但是,CALL也有一个缺点,就是它必须做一个用于返回的标记,这个有点类似于环境变量延迟,因此,有时候CALL外部命令时,它的效率也是十分差的。特别是在FOR中进行多个CALL时,它的效率会明显地下降。
因此,在大循环中避免使用GOTO只能当做一条不是很充分的条件,在许多情况下可以避免使用,但不是不能使用。还是那句话,在该使用GOTO的时候还是需要使用GOTO,在该使用CALL的时候还是需要使用CALL。灵活地熟练地应用两者才能在FOR中,在批处理中获得更大的发展空间。
作者: plp626    时间: 2011-9-6 23:05

三,合理使用临时文件
三,合理使用临时文件
1,从命令获取大量输入时,尽量使用临时文件for /f "delims=" %%a in ('dir /s /b \') do echo %%a
复制代码dir /s /b \>tmp.txt
for /f "delims=" %%a in (tmp.txt) do echo %%a
复制代码rem 因为前者是把“临时文件”放在内存中的,过量的内存占用将明显影响 cmd 的执行效率,后者却不用担心这一点。
2,前后存在内部命令或者语块时慎用管道(for /l %%a in (1 1 1000) do echo %%a)|sort
复制代码(for /l %%a in (1 1 1000) do echo %%a)>tmp.txt
sort tmp.txt
复制代码rem 前者的管道符前后存在语块,将开启新的 cmd 进程来执行此处的 for /l,所以降低了效率,此时改用临时文件就快了许多


没做测试,但从逻辑上讲,"for /f (file)do"也是把数据读入内存,临时文件是还要从硬盘上读取,写数据更花费时间,不知道原作者怎么测试的得出这些结论的;
作者: cjiabing    时间: 2011-9-7 00:09

回复 8# plp626


    感觉他说的话就是真理,没办法啊。还要再做多方面验证,不过许多都是很有价值的。
作者: wankoilz    时间: 2011-9-8 19:27

简洁明了,受益不少!




欢迎光临 批处理之家 (http://bathome.net./) Powered by Discuz! 7.2