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

[原创] 探讨批处理代码效率

为了处理一些windows不方便做的事情时,我们不得不选择cmd批处理来解决问题,
以逸待劳,来为我们服务。稍有一点了解批处理的都知道,cmd批处理是功能“强大”,但代码运行速度太慢。
    于是问题出来了,本来我们选择用cmd批处理的目的是为了更方便和更快速的,可是
cmd批处理的速度和我们期望目标成了矛与盾的较量。
    同一个问题批处理的解决办法,往往有多种,不同算法之间的速度相差非常之大,
为了提高cmd批处理程序,算法很关键,于是,都费尽脑汁,想方设法找一些巧妙的
方法。在注重算法的同时,往往忽略了或误解了最基本的问题:“批处理命令的效率”
    其实了解批处理命令的效率,有一个良好的BAT编写习惯,可以在为了好的算法
伤脑筋时,少走弯路。我根据在cn-dos所学到的简单总结了一下,欢迎各位指出错误。

声明:
以下仅为个人观点,有兴趣的就看看,并不代表权威数据,
特此声明,以免对大家有误导!

**********
因为不同的电脑速度相差太大,为了能有个统一标准,以@echo off 执行间为0.20毫秒为标准
**********
1. 预处理与运行速度
批处理的预处理已经有多人讨论过了,可以在论坛搜索“预处理”查看。这里就不详述,
只讨论预处理同效率的问题。
其实每行代码真正执行的时间不长(0.01~0.02毫秒),可是单单运行一行代码时,
最少也要0.19毫秒(如:注释行rem),为什么呢?我认为:这应该是批处理的运行的
“预处理”造成的,批处理最好的地方在这里却成了拌脚石,有没有办法解决这个问题呢?
有,把多行命令合成组执行,批处理对组()内的多行代码一次性预完成的。
()分组包括If () for () 和直接(),和用&并行,都具有同样的效果,能让系统一次性对多
条指令进行同时预处理,一组命令的用时为:一个预处理时长(0.19毫秒)+每条命令的执行时
长(0.01~0.02毫秒),也就是越多条命令合成一组,效率越高。for 的效率高的主要原因应该就在这里。
2. 管道操作与运行速度
管道操作对象有:程序(外部命令,三方工具等),内部命令(其实是命令解释器),
设备(空设备nul,标准输入输出设备con,打印设备prn,存储设备即常用的文件)
标准设备输入输出间的转换并不会占用很多时间,只有管道操作的对象为程序和内部命令时,
才会突然加大时间开销,如:
echo a                        0.41毫秒
echo a>>b.txt,       0.30毫秒
echo. a>>b.txt      1,30毫秒  (不知为什么这一点会多用那么多时间)
findstr “aa” a.txt    44.90毫秒
type a.txt |findstr “aa”   99.12毫秒
for /f %%a in (‘set #’) do (rem),  42.70毫秒
echo a|echo b     90.04毫秒
echo aa|findstr .*    88.84毫秒
echo a|sort      89.12毫秒
sort a.txt      45.45毫秒
abcd.com      12.50毫秒 (一个最直接退出的程序,非管道)
可见,程序和命令进行管道操作是需要很大资源的,为什么内部命令进行管道操作也要那么多的时间呢?
如:echo a|echo b
这可能是当前内部命令是不能接收管道数据的,而是调用了另一个命令解释器,再执行其中一个命令,
所以像调用了一个外部程序一样。for /f %%a in (‘command’) do 也应视为一次管道操作,
而使用“|”是进行了双管道操作所以用时多了一半。
3. 子程序调用时,选择文件还是函数
在cmd下,可以使用call :标号 来实现子过程调用,这种方法可以把多个子功能集中到一个BAT文件内,
进行内部调用。这种方法可以减少BAT文件的个数,可是要是考虑到效率问题时,
与调用外部BAT文件相比会是怎么样呢?测试结果:
Call :sub   3.51 毫秒
Call abc.bat  1.28 毫秒
出人意料!怎么外部调用会比内部调用更高效呢???
4. Call 各种方法中的效率问题
Call 的用法有多种,效率如下:
Call echo %%!n!b%%    1.64毫秒
for %%a in (!n!) do echo !%%ab!  0.63毫秒   看来用用for 来取代上句是可以加速度的
Call set aa=%%bb%%    1.47毫秒
Call :sub      3.51  毫秒  (空操作)
Call abc.bat     1.28 毫秒  (空操作)
附件: 您需要登录才可以下载或查看附件。没有帐号?注册
3

评分人数

    • 基拉freedom: 很不错 这种技术帖子 支持技术 + 1
    • tireless: (预处理一次) 有意思PB + 9
    • 随风: 有价值是分析!PB + 20 技术 + 1

多年后的膜拜……

TOP

回复 31# aloha20200628
大佬,最近我又看到了Batcher大佬的多行回退,但是并未在win10中实现,不知为什么,

TOP

回复 31# aloha20200628
谢谢,正想找这种代码呢!
本人已死,不用联系,要联系下来联系~

TOP

回复 31# aloha20200628
谢谢提供链接,内容确实精彩!!!

TOP

看这个十年前的老帖 http://www.bathome.net/thread-11799-1-1.html 可见当年批处理计算字符串长度的"技法峰值"
在此分享源网站 https://www.dostips.com 的这段经典代码(见以下代码段),其内还有两枚技术硬核》
一。句式 set "str=a!%~1!" 提高形参 %1 的容错率,一网打尽键盘所有可见字符
二。句式 (endlocal ... set /a %~2=%len%) 令局部变量亡前可续命给全局变量

附加几行代码》针对经典代码的测试/用法
  1. @echo off
  2. :[Loop] //测试代码 备注》调用子过程的形参须是变量名
  3. set "str=" &set/p str="输入一个字符串获取其长度:"
  4. if not defined str exit/b
  5. (call :strLen str sL)
  6. echo,长度=%sL%
  7. goto[Loop]
  8. :: 分享计算字符串长度的经典代码如下》
  9. ::      string [in]  - variable name containing the string being measured for length
  10. ::      len [out] - variable to be used to return the string length
  11. :: Many thanks to 'sowgtsoi', but also 'jeb' and 'amel27' dostips forum users helped making this short and efficient
  12. :: Created 20081122,changed 20101116,source https://www.dostips.com
  13. :strLen string len -- returns the length of a string
  14. (   setlocal enabledelayedexpansion
  15.     set "str=a!%~1!" &rem keep the a up front to ensure we get the length and not the upper bound,it also avoids trouble in case of empty string
  16.     set "len=0"
  17.     for /l %%a in (12,-1,0) do (
  18.         set /a "len|=1<<%%a"
  19.         for %%b in (!len!) do if "!str:~%%b,1!"=="" set /a "len&=~1<<%%a"
  20.     )
  21. )
  22. ( endlocal & rem return values
  23.     if "%~2" neq "" set /a %~2=%len%
  24. )
  25. exit /b
复制代码

TOP

本帖最后由 qixiaobin0715 于 2022-8-23 09:46 编辑

看了大佬们的代码,真是受益匪浅。
自己也提供一个思路,二分法。下面字符长度限定在1024之内,可根据具体情况自行调整。
由于水平有限,效率不在考虑范围之内:
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. set Var=123456789
  4. set a=512 256 128 64 32 16 8 4 2 1
  5. if defined Var (
  6.     set n=1
  7.     for %%i in (%a%) do (
  8.         if not "!Var:~%%i!"=="" (
  9.             set Var=!Var:~%%i!
  10.             set /a n+=%%i
  11.         )
  12.     )
  13.     echo,!n!
  14. ) else (
  15.     echo,Var is not defined
  16. )
  17. pause
复制代码

TOP

感谢分享!学习学习

TOP

回复 1# netbenton


echo. a>>b.txt      1,30毫秒  (不知为什么这一点会多用那么多时间)
之所以这里时间会多很多,是由于加了点后,相当于创建的文件前面要多一个空格符号。如果在for命令里面,数据多了,将是很恐怖的一个事情,会每一行前面加一个空格符号。

TOP

回复 26# 523066680


很好的算法,简单明了

TOP

路过,把老东西不被看好的再贴一次,不要BS我,只因为很久没说话了
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. set str=cn-dos.net
  4. set st2=Str%str%11111111111111111111111111111111111111111111111111111111111111111111111111111111
  5. set "st2=%st2:~0,83%"
  6. set n=!st2:Str%str%=-0!
  7. set /a n=80 %n:1=-1%
  8. echo,%n%
  9. pause
  10. ::个数可以是0 范围是0~80 的普通字符串。
复制代码

[ 本帖最后由 523066680 于 2009-12-11 08:37 编辑 ]
1

评分人数

    • neorobin: 之前没仔细看,现在看明白了, 哈哈, 好另类的 ...PB + 5

TOP

回复 12楼 的帖子

确实是很好的算法, 缺憾是当最大长度很大时, 也会需要很大的变量空间, len后面的标识符可用辅助代码生成无重复的序列

和 23 楼的想法一样, vva 变量是多余的, 我的改法更简明一些, 做个减法就行了
  1. @echo off&setlocal enabledelayedexpansion
  2. set aav=abcdefghijklmnopqrstuvwxyz不够的话文字也可以abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
  3. set 最大长度=80
  4. for /l %%a in (0,1,%最大长度%) do set /a len!aav:~%%a,1!=最大长度-%%a
  5. ::前面初始定义一次,后面可以任意使用,只要两个set 就可以取得字符串长度。
  6. ::但前提是,你要知道需计算的字符串中,最长字符串的长度。
  7. ::效率可想而知了!
  8. set str1=12345678
  9. set str2=abcdefghijklmn
  10. set var1=!str1!!aav!
  11. set var2=!str2!!aav!
  12. set/a var1=len!var1:~%最大长度%,1!,var2=len!var2:~%最大长度%,1!
  13. echo !str1! 长度为:!var1!
  14. echo !str2! 长度为:!var2!
  15. pause
复制代码

[ 本帖最后由 neorobin 于 2009-12-11 06:14 编辑 ]

TOP

我觉得find和findstr是最慢的,好像没都耐心试过这两个查找东东的,发现dir和for比她两还快!~当然for的目录多了它也跑不动,多几个for更是慢。

关于临时文件,有时候你为了看运行结果得用pause和>>,似乎那样代码就没效率了

TOP

原帖由 netbenton 于 2009-6-2 01:31 发表
在批处理程序设计中,取字符长度最高效的方法@echo off&setlocal enabledelayedexpansion
set aav=abcdefghijklmnopqrstuvwxyz不够的话文字也可以abcdefghijklmnopqrstuvwxyzABCDEF ...

不用把aav倒过来,可以这样:
set str1=12345678
set var1=!aav:~,81!!str1!
set /a var1=len!o:~-81,1!

[ 本帖最后由 tireless 于 2009-6-7 12:29 编辑 ]

TOP

为了充分保证效率千万不要使用下面两个命令
call echo.....
call set a=...

万不得已要call set n=数字
一定要call set/a n=数字    //这个效率还是很高的是set/a 一半的效率
我发现call set a=... 的执行效率比call子过程还要低10倍以上!

TOP

返回列表