Board logo

标题: [系统相关] 批处理变量表机制的猜测及测试 [打印本页]

作者: caruko    时间: 2011-4-22 15:08     标题: 批处理变量表机制的猜测及测试

前面ZM兄谈到变量越多,批执行效率越低的问题。
我做了如下代码测试:

一、
环境中只有1个变量时,速度非常快,一万次调用,耗时不到1秒。

二、
生成一万个变量_1到_10000作为测试环境,测试都执行一万次,现象如下:

1:调用"_1",用时5.5秒。
2:调用"_1000",用时5.5秒。
3:调用"_9999",用时25-27秒。
4:调用"_9",用时25-27秒。
5:调用"_1 To _10000"各一次,总共也是一万次,耗时16秒。
6:调用"_5000"以及"_5","_50",三者用时都在15秒左右。
7:调用字符变量"_x",用时25-27秒。

都是从一万个变量中读取其中一个,"_1"与"_9999"的速度差距巨大!!

直观的可以看20楼的图。

测试结论(猜测)::
批处理遍历了一个变量(名)表,排在前列的读取速度快,排在后面的读取速度慢,而且从表开始到结束,读取耗时是匀速增加的。表的内容是经过排序的,与"set _"的输出一致。

测试代码:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /l %%a in (1,1,10000) do set _%%a=1
  3. echo %time%
  4. for /l %%a in (1 1 10000)do set/a _9+=2
  5. echo %time%
  6. for /l %%a in (1 1 10000)do set/a _10000+=2
  7. echo %time%
  8. pause
复制代码
执行结果
  1. 20:58:43.03
  2. 20:59:04.87
  3. 20:59:09.26
  4. 请按任意键继续. . .
复制代码

作者: batman    时间: 2011-4-22 15:55

  caruko很有研究和较真的精神,先在这里赞一个!的确,我们在平常一直要求新手写代码的执行效率要高。

这只是为了大家养成一个好的习惯而已。而其实我们在实际中处理问题都是现写现用,有时根本没有去考虑代码

执行效率的问题,只求能把问题解决了就好。毕竟程序再怎么慢也要比人手动快n倍,说句俏皮话:让代码飞一会

儿。当然,在学习研究的时候我们还是要注重这个代码执行效率的问题。

  当代码运行时产生大量的变量肯定是对运行时间有影响的,因为变量是储存在内存中的,也就是说变量越多

程序消耗的内存就越大,大家都知道当内存占用率接近100%时,用电脑做任何事情都是很慢很慢的,在这里

是一个道理,当程序运行时产生足够多的变量时,它就会占去绝大部分的内存空间而导致电脑变慢甚至死机(

不信的话就用批去定义一亿个变量)。大家注意在这里变量所影响的并不是程序的执行效率而是电脑的运算处

理速度,所以个人认为只要电脑的内存足够大,大量的变量的存在是不会对程序的执行效率带来多大的影响的。
作者: CrLf    时间: 2011-4-22 16:13

本帖最后由 zm900612 于 2011-4-22 16:16 编辑

cmd的cpu占用(低于百分五十)和内存占用(低于XXMB,具体值忘了)都是有上限的,无论用几G的内存都不可能超过那个值,题外话,我以前有过利用双cmd进程并行实现最大程度利用cpu的想法,但是当时背景不对,效果不显著。
作者: caruko    时间: 2011-4-22 16:24

本帖最后由 caruko 于 2011-4-22 16:28 编辑

并不只是内存的问题。而是CMD的变量存储读取机制问题。

否者,为什么调用第一个变量的速度比调用最后一个变量的速度快几倍?

而且,一定程度上解释了,set 存放变量的时候是排序好的。
并且 set _ 这类代码的作用,说明set 存放变量很可能是 数据库 的方式。
作者: CrLf    时间: 2011-4-22 16:51

我上个月做过一些关于效率的测试,原因不解释...部分结果如下:
1、正如楼主的结果证明的那样,存在大量变量时,读取变量的效率会极大地降低,不含变量时,命令效率不变。
2、单纯读取变量快于变量偏移,变量偏移快于变量替换,并且变量偏移的效率与偏移量关系不大

另外赠送两个:
3、外部命令启动速度都差不多,而不仅仅只有findstr慢,另外,文本写入速度与外部命令启动速度相当,读取速度的测试结果忘了
4、预处理!str!比%%a快
作者: caruko    时间: 2011-4-22 18:00

其实我不是想只说明变量越多,速度越慢的问题,这个经常写bat的一般都有了解。

我希望大家谈谈对“变量的存储、读取” 方面猜测理解。

环境:
先设置1万个变量,从_1到_10000。

测试1:调用“_9999”这个变量1万次,再 调用“_1”这个变量1万次,耗时比 27:6 。

测试2:调用_1,_1000,_10000这3个变量执行1万次计算,3者耗时相差仿佛。调用 _99,_999,_9999,以及唯一的一个字符变量"_x",4者调用耗时也相差仿佛。

测试3:将_1--_10000这一万个变量都调用一次 ≈ 调用 _5 | _5000 这些变量一万次。


从上面测试结果来看:
1.  "变量增多,耗时越多"的原因,跟内存的关系不大!!因为1万个小变量不会导致内存不够,其它程序语言变量没有多到阀值,多少也不会有明显影响。

2.  变慢的原因可能跟批处理在预处理变量时,进行变量名查找匹配的速度有关。

3.  批处理内应该有一张表,按照排序好的顺序,存储着变量名(也可能含有以字符类型存储的变量值,或者内存指针)。

4.  批处理在预处理变量时,遍历变量表,取出匹配的变量名,再取得该变量的值。

5.  批处理在读取变量表时,很可能有一个复制(或者别的)操作(这可能是变量越多越慢的另一个原因)。 因为从一万个元素的Array(10000)数组中读取Array[0] 的速度,绝对不会比从Array(1)中读取Array[0]的速度慢太多。而这在批处理中的差距是几千倍。

大家有什么看法??
作者: plp626    时间: 2011-4-22 18:16

1# caruko


对变量的结论你是怎么得出的? 猜测的话你的控制实验现象是什么?

你看一些介绍中缀表达式计算的C代码,扩展后可以操作变量,可以解释文本里的数学公式,并有简单的编程功能,都是堆栈的方式,每一条命令行语句,对变量的读取,就是读地址,预处理的时候变量的字符串已经成为常量,cmd把这个常量转换为散列地址,你赋值时后它已经放在哪,然后读变量的时候cmd又把这个变量解释为散列地址,就直接取值了,若没定义过它就返回空。只是谁也没见识过cmd的源代码,汇编也是更是门外汉。

cmd里的random,errorlevel,date,time变量又不同于我们定义的变量,对于延迟处理我们还是靠现象猜测,有时候我给cmd发一条好长的复合语句,它会报告存储空间不足,无法处理此命令,我们是在黑暗中摸索,较真的话,很耗费时间精力的。
作者: plp626    时间: 2011-4-22 18:18

我们若为提高效率,记住这条实用的:

对数值的单一赋值,set 比set/a 快25%左右。
作者: plp626    时间: 2011-4-22 18:26

有个给大伙的建议,我们在做cmd执行效率的时候,能不能把耗费时间的秒换算成是echo off(或其他某条语句,大伙可以指定可统一标准)语句耗费时间的多少倍,这样方便参考。

都说自己运行耗费了多少秒,那只是时间,没要参考价值的。当然你可以给出自己的cpu型号,内存,但即使这样各人的机子也不同。

效率是单位时间所执行的任务才对。
作者: CrLf    时间: 2011-4-22 18:27

我们若为提高效率,记住这条实用的:

对数值的单一赋值,set 比set/a 快25%左右。
plp626 发表于 2011-4-22 18:18

这个没留意过,学习了
作者: caruko    时间: 2011-4-22 18:42

C的变量,内存的读取,我有一定了解。但是C是编译程序,静态变量一般直接翻译成地址。

如果调用一万个变量 比 调用一万个变量中的 一个变量 速度慢,或许可以解释成CPU缓存与寻址之类的说法。


但是,从 读取 _1 跟 _9999 的速度不一样,以及_9,_5000等调用测试,可以看出:
批处理遍历了一个表,排在前列的读取速度快,排在后面的读取速度慢,而且从表开始到结束,读取耗时是匀速增加的。表的内容是经过排序的,与"set _"的输出一致。

这个结论很容易验证,我已给出代码,大家都可以自己试试,调用不同位置的变量,看看速度有那些差距。


验证过上面的代码后:
如果真的有“变量表”存在,那么P跟C的变量存储方式肯定不同。
set x=1 速度快于 set /a x=1 ,则很可能说明:变量是以字符类型存储的。如果是指针,那么速度应该差不多。
作者: plp626    时间: 2011-4-22 18:48

11# caruko


你怎么排除预处理的因素?
作者: caruko    时间: 2011-4-22 18:53

本帖最后由 caruko 于 2011-4-22 19:00 编辑

12# plp626

for /l %%i in (1,1,10000)  set /a _10000+=2

for /l %%i in (1,1,10000)  set /a _9999+=2

预处理会有很大差距吗?
_9 , _99 , _9999  耗时几乎相同
_1,_100,_10000 耗时也几乎相同

但是 _9 >> _10000,可否说明,跟预处理无关?

1万次 _9 耗时 23.1 秒
1万次 _10000 耗时 5.3 秒

每次的执行时间虽然不固定,但是巨大的差距足以说明问题。
作者: batman    时间: 2011-4-22 19:02

大家是不是对代码执行效率有了误解?如对一个变量操作的时间为一个单位,同时对10000个变量操作的时间小于或等于一万个单位,这样的执行效率则是正常。但如对一个变量操作的时间为一个单位,而对10000个变量操作的时间远远大于一万个单位,这样的执行效率就不是正常的,也才能说因为大量的变量的存在让代码执行效率有了改变。
    至于caruko所提出的读取_1和_9999的速度不一样,我认为存在误区,以下面的代码来证明:
  1. @echo off
  2. for /l %%a in (1,1,10000) do set "_%%a=a"
  3. echo %time%
  4. echo %_1%
  5. echo %time%
  6. echo %_9999%
  7. echo %time%
  8. pause>nul
复制代码
我本机上一次运行结果如下(几乎都是不需要时间的,何来的差距很大):
18:59:59.82
a
18:59:59.82
a
18:59:59.82
作者: batman    时间: 2011-4-22 19:13

13# caruko
代码:
  1. @echo off
  2. echo %time%
  3. for /l %%a in (1,1,10000) do set /a _9+=1
  4. echo %time%
  5. for /l %%a in (1,1,10000) do set /a _10000+=1
  6. echo %time%
  7. pause>nul
复制代码
运行结果:
19:12:49.35
19:12:49.57
19:12:49.79
哪来的巨大的相差?
作者: caruko    时间: 2011-4-22 19:16

14# batman

你只执行一次,当然看不出来。 循环一万次试试??
  1. @echo off
  2. for /l %%a in (1,1,10000) do set "_%%a=a"
  3. echo %time%
  4. for /l %%a in (1,1,10000) do set /a _1+=1
  5. echo %time%
  6. for /l %%a in (1,1,10000) do set /a _9999+=1
  7. echo %time%
  8. pause>nul
复制代码

作者: batman    时间: 2011-4-22 19:17

16# caruko
看我13楼是不是10000次?
作者: caruko    时间: 2011-4-22 19:23

本帖最后由 caruko 于 2011-4-22 19:27 编辑

17# batman


你13楼代码,没有初始环境!
在最前面加一句
for /l %%a in (1,1,10000) do set /a _%%a=1

[attach]3755[/attach]
作者: caruko    时间: 2011-4-22 19:38

18# plp626


你说的这些,就跟《量子力学》与《牛顿定律》一样。
从相对恒定的常规环境中的测试结论,得出的《牛顿定律》仍然可以说明常见现象。
扯那么深远,的确我测试不出来。
作者: caruko    时间: 2011-4-22 20:01

楼上的大哥...
我说了,前提是有一万个变量的测试环境。 然后取第一个_1跟最后一个_9999。

你的代码,相当于环境中总共只有2个变量。
也就是只差一个位置,差距在那里?

请仔细看看我20楼的图,分析一下代码。
作者: caruko    时间: 2011-4-22 20:02

本帖最后由 caruko 于 2011-4-22 20:09 编辑

也就是说,在你的代码中,第2行中添加一句代码。
for /l %%a in (1,1,10000) do set "_%%a=1"

对了,一万就足够了。
作者: caruko    时间: 2011-4-22 20:15

25# plp626


好吧,被你打败了。
你的 CALL执行完,结果为0,没分析你的算法。
但请你直接ECHO %time%吧


20:14:04.89
20:14:26.03
20:14:30.26
请按任意键继续. . .
作者: cjiabing    时间: 2011-4-22 20:48

本帖最后由 cjiabing 于 2011-4-22 20:53 编辑

虽然俺不是专家,不过看见你们瞎忙乎,很想和你们谈谈一些个人认识。
      计算机有 0和1 两种基本信号,编码后组成一些最基本的字符列阵,这些字符列阵在系统中被其它程序广泛使用。具体搜索下“计算机编码”、“字符集”、“进制”。字符集应该这就是你们谈到的“字符表”了。这些字符表、字符集也是按照计算机编码顺序组成的,如果记得当年的那些什么码输入法你可能会幡然醒悟。但假如没有也不要紧,系统一般自带了这些字符映射信息。
      通常,XP系统里的字符集位于:【开始菜单——附件——系统工具——字符映射表】,真实路径位于:【  %SystemRoot%\system32\charmap.exe  】,可在运行里输入【 charmap 】打开。这就是字符映射表。将鼠标随便移动到字符表上,稍加停留,或者点击,你就能看到任意字符在字符表中的ID。然后你再看看排在最前面的几个是什么字符,你就知道可能的读取速度了。一般我们认为,可能受到编码进制的影响,排在前面的编码顺序要比后面的快。当然,这些在平时是感觉不到的,只有像你们折磨CPU和内存的时候它才表现出来。——这个可能需要更多的证据。
      再谈谈你们测试方法存在的问题。当你们通过产生大量变量来处理问题的时候,我就想到了洪水对河道的霸占。当暴雨剧降,河水泛滥,河道无法容纳足够的河水,于是溢出。而当暴雨过去,河水的消退也需要一定的时间。从变量的增加到变量的清除,内存或者cpu的处理都需要时间和空间,而这个时间是不能用增加时和清除时的时间简单相加和平均的来算的。
      再者,批处理融入到系统内部,它处理的时候应该是内部处理,只是它有许多外部接口。做个试验,在一个简单循环程序中,使用“@echo off”会明显比不使用该开头的程序运行速度快,因为后者运行时进行了重定向。
作者: plp626    时间: 2011-4-22 21:01

26# caruko


老了,把函数的et忘了改了成%3了;
  1. :etime
  2. set/a %3=1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!,%3+=-6000*("%3>>31")
复制代码
你的试验数据属实,不过我把你才试验改进下,不是相差4.5倍,是相差70倍,我得到一个很有意义的结论。
这个结论对我们在进行大量变量赋值时具有很现实的指导意义。

待会告诉大家。

caruko为大家做了一个很有意义的试验。暂一个。
作者: plp626    时间: 2011-4-22 21:26

本帖最后由 plp626 于 2011-4-23 09:41 编辑

结论就是:                                            【结论就是,只看测试代码运行结果,或者直接跳到40楼】

endlocal(不是setlocal)释放掉大量垃圾变量后赋值操作才会变快

代码比较如下:
  1. @echo off
  2. :: 测试一,垃圾变量在变量空间一内,变量二空间内变量的赋值耗时测试
  3. setlocal enabledelayedexpansion
  4. call:tt
  5. call:etime t1 t2 one
  6. set one
  7. for /l %%a in (1,1,10000) do set _%%a=1
  8. endlocal&Set one=%one%&setlocal enabledelayedexpansion
  9. call:tt
  10. call:etime t1 t2 two
  11. set two
  12. set/a pp=two/one
  13. echo 倍数: !pp!
  14. pause
  15. :tt -------------------------- sub -----------------------------
  16. set t1=%time%
  17. for /l %%a in (1 1 10000)do set/a _9+=2
  18. set t2=%time%
  19. goto:eof
  20. :etime
  21. set/a %3=1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!,%3+=-6000*("%3>>31")
  22. goto:eof
复制代码
  1. @echo off
  2. :: 测试二,垃圾变量在变量空间二而内,变量空间二内变量的赋值耗时测试。
  3. setlocal enabledelayedexpansion
  4. call:tt
  5. call:etime t1 t2 one
  6. set one
  7. endlocal&Set one=%one%&setlocal enabledelayedexpansion
  8. for /l %%a in (1,1,10000) do set _%%a=1
  9. call:tt
  10. call:etime t1 t2 two
  11. set two
  12. set/a pp=two/one
  13. ECHO 倍数:!pp!
  14. pause
  15. :tt -------------------------- sub -----------------------------
  16. set t1=%time%
  17. for /l %%a in (1 1 10000)do set/a _9+=2
  18. set t2=%time%
  19. goto:eof
  20. :etime
  21. set/a %3=1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!,%3+=-6000*("%3>>31")
  22. goto:eof
复制代码

作者: caruko    时间: 2011-4-22 21:57

29# plp626


你的代码,好像不是 全局 与 局部 的关系啊。
而是2个相对独立的局部。

这样的意义就相当于:
当我们需要大量变量参与计算(计算后这大量的变量需要废弃),而我们只需要计算后的少数几个结果,那么可以setlocal,减少之后其它语句的运行耗时。
作者: plp626    时间: 2011-4-22 22:09

29# plp626


你的代码,好像不是 全局 与 局部 的关系啊。
而是2个相对独立的局部。

这样的意义就相当于:
当我们需要大量变量参与计算(计算后这大量的变量需要废弃),而我们只需要计算后的少数几个结果, ...
caruko 发表于 2011-4-22 21:57


你说的对,是两个相对独立的局部。。
作者: caruko    时间: 2011-4-22 22:12

是啊,没考虑过setlocal对此有没有影响。

ZM谈起大量变量影响效率,自己当初也做过生命游戏,做出过耗时3分钟跟15秒的不同版本,但当时没有深想。

现在回想起来,感觉变量大量存在导致的效率问题,超出了其它程序语言的N倍,而且SET 自带排序输出(耗时时很少) 。就猜想set中的变量,很可能是有序存储的。

然后就做了测试,发现了新的问题。
另外就是 _1 跟 _9 之间的效率差距问题,到底是否可能存在有序的变量表?
作者: CrLf    时间: 2011-4-22 22:43

用1和10000来做对比测试不太严谨吧,因为字符长度会影响预处理耗时,不如改成类似10000和16383的数(十进制和二进制位数均相同)
plp626兄台的测试代码我有点看不懂啊,endlocal之后不是不存在刚刚制造饿垃圾变量了么?
作者: CrLf    时间: 2011-4-22 22:44

题外话,好像本贴标题变了四五次了...
作者: caruko    时间: 2011-4-22 22:51

36# zm900612

我测试过 set /a _1000+=2 跟 set /a _9999 +=2
排除了字符长度问题,结果仍然。

因为很多人没看懂,所以代码就简单写了2个, _9 耗时比 _10000 长,最能说明问题。

耗时的多少,跟排序后的先后顺序有关。
作者: plp626    时间: 2011-4-22 23:06

用1和10000来做对比测试不太严谨吧,因为字符长度会影响预处理耗时,不如改成类似10000和16383的数(十进制和二进制位数均相同)
plp626兄台的测试代码我有点看不懂啊,endlocal之后不是不存在刚刚制造饿垃圾变量了 ...
zm900612 发表于 2011-4-22 22:43



记得你说过自己不怎么用call,那可想而知你更是少用endlocal;

endlocal执行后,就把垃圾变量“释放”了。
把这段代码执行下,看看运行结果:
@echo off
setlocal&set plp=1&endlocal&set plp
pause
作者: CrLf    时间: 2011-4-22 23:23

39# plp626


确实没怎么用过...
不过endlocal的基本作用我是知道的,它会清除最近一个setlocal后的所有变量改动,但是我对“栈内数据存储操作和不受栈外垃圾变量影响”这句不太理解,为什么说是栈外变量呢?“栈外”存在变量吗?那些变量不是应该被清空了吗?
作者: plp626    时间: 2011-4-22 23:26

本帖最后由 plp626 于 2011-4-23 09:39 编辑
用1和10000来做对比测试不太严谨吧,因为字符长度会影响预处理耗时,不如改成类似10000和16383的数(十进制和二进制位数均相同)
plp626兄台的测试代码我有点看不懂啊,endlocal之后不是不存在刚刚制造饿垃圾变量了 ...
zm900612 发表于 2011-4-22 22:43


说明,此楼以上到29楼的所有结论皆无效!
=====================================================
我感到自己有罪了,你们俩现在还没看出我那两个代码测试的结论是错误的,哎,苦了看帖的人了;

刚给你回复了帖子,我才想到有已经endlocal了!那就是说测试一代码没有垃圾变量了;

所以测试代码一不能叫垃圾变量在变量空间一内,赋值在二,而是,垃圾变量在空间一内生成,刚生成完又被cmd释放清空了。

所以测试一和二代码等效的来说就是都在同一个变量空间赋值;

测试一是在一个释放了垃圾变量的空间相等于清空的空间赋值,测试二则是在已经充满垃圾变量的空间进行变量赋值;

29楼那个结论“用setlocal可以减少其他变量赋值的耗费时间”真的惹笑话了!!

正确结论是 垃圾变量越多赋值越耗时,你没办法了~~~~除非你endlocal把他释放掉。

搞了这么久,就搞出来这个常识性的结论,是在汗!
作者: batman    时间: 2011-4-22 23:29

真的是没有调查就没有发言权,我之前有点想当然了。。。

但是按理来说,变量在内存中的存储应该是堆栈,先入的后出,也就是说在定义_1-_10000的变量时,_1变量

在堆栈中应该是放在堆栈的最下面的,而_10000变量是在堆栈的顶部,如果遵循先入后出的原则,则读取_10

000变量的时间是最短的,而读取_1变量的时间是最长的。

但是从caruko的测试中我们可以看出读取_1和_10000变量的时间是差不多的,而读取_9和_9999变量的时

间也是差不多的,但是读取_1和_9以及读取_9999和_10000的时间却出现了很大的相差。这不得不让我们

从cmd默认排序机制上猜想,在内存中变量_1至_10000的排列是_9999置于最底部,接着是_9998.......

_9990,_999,_9989....._9980,_998,_9979......._10000,_1000,_100,_10,_1,_1是置于最顶部。

这一猜测几乎可以推翻开始堆栈的设想,除非在堆栈中变量名也存在排序的机制。真的希望有牛人能找到变量

在内存的存放地址,这样真相才能大白于天下。
作者: terse    时间: 2011-4-22 23:51

本帖最后由 terse 于 2011-4-23 00:25 编辑

猜测下   定义变量后 CND是否开始 在变量表查找变量 然后分析是否已经定义  再对变量表重新排列 整个过程的快慢 是否就是取决于变量表复杂度,
貌似以前翻到过类似贴 解释变量的存储

补充:如果变量如不是已定义, 则寻找一个空的位置来存放 所以相对旧的变量反而查找快 反之cmd 寻找先前变量位置 这个寻找过程的快慢和执行效率相关
作者: caruko    时间: 2011-4-22 23:52

本帖最后由 caruko 于 2011-4-23 00:00 编辑

变量数目越多,操作越慢。 那么基本的设想就是,每次读取一个变量,都对整个“变量组”做了某种操作。

如果假设:这些变量是一个数组,那么以指针的方式读取Array(10000)中的Array[0],与读取Array(1)中的Array[0]并不会有很大的差别,除非在每次取值的时候,都将整个数组复制了一次(或者类似操作才会使得耗时成倍增加)。

还有一种猜想,就是类似数据库的存储方式。这样才会出现在变量大量增加时,耗时成倍增加。同时兼顾排序,输出所有匹配前几个字符的变量(如set _,set _@#等),等功能。


所以,“变量-耗时”的问题估计主要有两个方面。
1:  每次读取一个变量,都操作了整个“变量组”,体现在“总变量多少--耗时”的关系。
2:  读取变量时,会有一个查找操作,查找耗时跟排序位置有关,体现在“变量排序位置--耗时”的关系。
作者: plp626    时间: 2011-4-23 00:11

本帖最后由 plp626 于 2011-4-23 00:43 编辑

做了一个变量赋值除了与已有变量数量有关外还和字符串的数据有关,但不是单纯的按ascii码顺序,还没找到规律,大家运行这个代码看看合字符串的顺序

_900到_999这样的变量是按照ascii码顺序的,耗时比值不是单调的。暂对规律不做猜想
  1. @echo off
  2. for /l %%a in (900 1 999)do call:xp %%a _ 已定义_[1-1000]再定义_?的耗时与直接定义_?
  3. 变量
  4. echo -------------------
  5. for /l %%a in (900 1 999)do call:xp %%a # 已定义#[1-1000]再定义_?的耗时与直接定义_?
  6. 变量
  7. pause
  8. :xp
  9. setlocal enabledelayedexpansion
  10.         call:tt
  11.         call:etime t1 t2 one
  12.         for /l %%a in (1,1,1000) do set %2%%a=1
  13.         call:tt
  14.         call:etime t1 t2 two
  15. set/a pp=two/one,r=(two%%one)*10/one
  16. echo 变量: _%1 %3耗时比: !pp!.%r%
  17. endlocal&goto:eof
  18. :tt -------------------------- sub -----------------------------
  19. set t1=%time%
  20. for /l %%a in (1 1 1000)do set/a _%1=1
  21. set t2=%time%
  22. goto:eof
  23. :etime
  24. setlocal enabledelayedexpansion
  25. set/a rt=1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!,%3+=-6000*("%3>>31")
  26. endlocal&set %3=%rt%&goto:eof
复制代码

作者: caruko    时间: 2011-4-23 00:24

你这样,应该还有很多CALL的耗时在内。
作者: plp626    时间: 2011-4-23 00:26

上面那个代码是用set/a定义_?,下面用直接用set,不带参数定义,看看运行结果:
  1. [code]@echo off
  2. for /l %%a in (900 1 999)do call:xp %%a _ 已定义_[1-1000]再定义_?的耗时与直接定义_?变量
  3. echo -------------------
  4. for /l %%a in (900 1 999)do call:xp %%a # 已定义#[1-1000]再定义_?的耗时与直接定义_?变量
  5. pause
  6. :xp
  7. setlocal enabledelayedexpansion
  8.         call:tt
  9.         call:etime t1 t2 one
  10.         for /l %%a in (1,1,1000) do set %2%%a=1
  11.         call:tt
  12.         call:etime t1 t2 two
  13. set/a pp=two/one,r=(two%%one)*10/one
  14. echo 变量: _%1 %3耗时比: !pp!.%r%
  15. endlocal&goto:eof
  16. :tt -------------------------- sub -----------------------------
  17. set t1=%time%
  18. for /l %%a in (1 1 1000)do set _%1=1
  19. set t2=%time%
  20. goto:eof
  21. :etime
  22. setlocal enabledelayedexpansion
  23. set/a rt=1!%2:~-5,2!!%2:~-2!-1!%1:~-5,2!!%1:~-2!,%3+=-6000*("%3>>31")
  24. endlocal&set %3=%rt%&goto:eof
复制代码
[/code]
作者: plp626    时间: 2011-4-23 00:31

本帖最后由 plp626 于 2011-4-23 01:11 编辑
你这样,应该还有很多CALL的耗时在内。
caruko 发表于 2011-4-23 00:24

call的耗时不会计算在内。都是开始时间和结束时间存到变量,然后结束后计算的,再保存到变量内。
作者: batman    时间: 2011-4-23 00:32

可能以下代码能说明一点问题:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /l %%a in (1,1,1000) do set /a "_%%a=1"
  3. for /l %%a in (1,1,100) do (
  4.     set "t=!time!"
  5.     for /l %%b in (1,1,10000) do set /a _%%a+=1
  6.     set "t1=!time!"&set "a="
  7.     if "!t:~,2!" lss "!t1:~,2!" set "a=+24"
  8.     set /a ".%%a=(!t1:~,2!-!t:~,2!!a!)*360000+(1!t1:~3,2!%%100-1!t:~3,2!%%100)*6000+(1!t1:~6,2!%%100-1!t:~6,2!%%100)*100+(1!t1:~-2!%%100-1!t:~-2!%%100)"
  9. )
  10. set .
  11. pause>nul
复制代码
这是将_1到_100变量计算10000次的时间(ms)用.1到.100变量储存然后列出,本机上运行结果如下:
  1. .1=50
  2. .10=54
  3. .100=69
  4. .11=54
  5. .12=57
  6. .13=57
  7. .14=59
  8. .15=63
  9. .16=62
  10. .17=66
  11. .18=67
  12. .19=70
  13. .2=62
  14. .20=68
  15. .21=70
  16. .22=72
  17. .23=73
  18. .24=75
  19. .25=78
  20. .26=78
  21. .27=80
  22. .28=85
  23. .29=82
  24. .3=78
  25. .30=83
  26. .31=85
  27. .32=87
  28. .33=88
  29. .34=89
  30. .35=90
  31. .36=96
  32. .37=93
  33. .38=97
  34. .39=100
  35. .4=91
  36. .40=97
  37. .41=100
  38. .42=101
  39. .43=103
  40. .44=105
  41. .45=108
  42. .46=109
  43. .47=111
  44. .48=114
  45. .49=116
  46. .5=106
  47. .50=112
  48. .51=114
  49. .52=118
  50. .53=118
  51. .54=119
  52. .55=123
  53. .56=124
  54. .57=126
  55. .58=127
  56. .59=128
  57. .6=119
  58. .60=130
  59. .61=128
  60. .62=133
  61. .63=133
  62. .64=134
  63. .65=137
  64. .66=138
  65. .67=140
  66. .68=141
  67. .69=144
  68. .7=131
  69. .70=143
  70. .71=146
  71. .72=146
  72. .73=147
  73. .74=152
  74. .75=151
  75. .76=154
  76. .77=154
  77. .78=156
  78. .79=159
  79. .8=145
  80. .80=158
  81. .81=160
  82. .82=161
  83. .83=166
  84. .84=175
  85. .85=194
  86. .86=178
  87. .87=174
  88. .88=173
  89. .89=177
  90. .9=159
  91. .90=173
  92. .91=175
  93. .92=176
  94. .93=180
  95. .94=180
  96. .95=182
  97. .96=185
  98. .97=184
  99. .98=191
  100. .99=187
复制代码
从这个运行结果可以看出确实变量名的存储和读取是按照cmd默认序列进行的。
作者: caruko    时间: 2011-4-23 01:11

50# batman


似乎,只1位的数字,耗时都会相对减少,预处理的原因?
作者: batman    时间: 2011-4-23 01:17

再测试(这下等得我。。。。)
代码:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /l %%a in (1,1,10000) do set /a "_%%a=1"
  3. for /l %%a in (1,1,100) do (
  4.     set "t=!time!"
  5.     for /l %%b in (1,1,10000) do set /a _%%a+=1
  6.     set "t1=!time!"&set "a="
  7.     if "!t:~,2!" lss "!t1:~,2!" set "a=+24"
  8.     set /a ".%%a=(!t1:~,2!-!t:~,2!!a!)*360000+(1!t1:~3,2!%%100-1!t:~3,2!%%100)*6000+(1!t1:~6,2!%%100-1!t:~6,2!%%100)*100+(1!t1:~-2!%%100-1!t:~-2!%%100)"
  9. )
  10. set .
  11. pause>nul
复制代码
运行结果:
  1. .1=389
  2. .10=390
  3. .100=413
  4. .11=406
  5. .12=426
  6. .13=439
  7. .14=466
  8. .15=473
  9. .16=502
  10. .17=514
  11. .18=520
  12. .19=546
  13. .2=528
  14. .20=520
  15. .21=537
  16. .22=555
  17. .23=577
  18. .24=590
  19. .25=606
  20. .26=625
  21. .27=647
  22. .28=652
  23. .29=677
  24. .3=654
  25. .30=657
  26. .31=678
  27. .32=697
  28. .33=717
  29. .34=736
  30. .35=738
  31. .36=758
  32. .37=786
  33. .38=799
  34. .39=810
  35. .4=782
  36. .40=791
  37. .41=804
  38. .42=823
  39. .43=841
  40. .44=855
  41. .45=872
  42. .46=887
  43. .47=906
  44. .48=926
  45. .49=943
  46. .5=918
  47. .50=918
  48. .51=961
  49. .52=969
  50. .53=978
  51. .54=992
  52. .55=1011
  53. .56=1018
  54. .57=1033
  55. .58=1059
  56. .59=1067
  57. .6=1050
  58. .60=1052
  59. .61=1071
  60. .62=1085
  61. .63=1107
  62. .64=1118
  63. .65=1136
  64. .66=1154
  65. .67=1176
  66. .68=1200
  67. .69=1216
  68. .7=1179
  69. .70=1192
  70. .71=1206
  71. .72=1224
  72. .73=1242
  73. .74=1254
  74. .75=1280
  75. .76=1302
  76. .77=1322
  77. .78=1324
  78. .79=1345
  79. .8=1330
  80. .80=1356
  81. .81=1372
  82. .82=1366
  83. .83=1387
  84. .84=1395
  85. .85=1425
  86. .86=1444
  87. .87=1452
  88. .88=1461
  89. .89=1483
  90. .9=1455
  91. .90=1473
  92. .91=1486
  93. .92=1520
  94. .93=1522
  95. .94=1536
  96. .95=1549
  97. .96=1566
  98. .97=1586
  99. .98=1644
  100. .99=1646
复制代码

作者: neorobin    时间: 2011-4-23 01:27

懒人关注: 等各位阶段性的研究成果, 支持!
作者: batman    时间: 2011-4-23 01:36

本帖最后由 batman 于 2011-4-23 01:38 编辑

接着测试(又是一次头大的等待)
代码:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /l %%a in (1,1,10000) do set /a "_%%a=1"
  3. for /l %%a in (1,1,9) do (
  4.     set "num="
  5.     for /l %%b in (1,1,4) do (
  6.         set "num=!num!%%a"
  7.         for %%a in (!num!) do (
  8.             set "t=!time!"
  9.             for /l %%b in (1,1,10000) do set /a _%%a+=1
  10.             set "t1=!time!"&set "a="
  11.             if "!t:~,2!" lss "!t1:~,2!" set "a=+24"
  12.             set /a ".%%a=(!t1:~,2!-!t:~,2!!a!)*360000+(1!t1:~3,2!%%100-1!t:~3,2!%%100)*6000+(1!t1:~6,2!%%100-1!t:~6,2!%%100)*100+(1!t1:~-2!%%100-1!t:~-2!%%100)"
  13.        )
  14.     )
  15. )
  16. set .
  17. pause>nul
复制代码
运行结果:
  1. .1=393
  2. .11=440
  3. .111=409
  4. .1111=411
  5. .2=525
  6. .22=559
  7. .222=566
  8. .2222=562
  9. .3=657
  10. .33=711
  11. .333=717
  12. .3333=722
  13. .4=796
  14. .44=866
  15. .444=877
  16. .4444=876
  17. .5=928
  18. .55=1019
  19. .555=1036
  20. .5555=1028
  21. .6=1065
  22. .66=1166
  23. .666=1181
  24. .6666=1183
  25. .7=1191
  26. .77=1317
  27. .777=1336
  28. .7777=1342
  29. .8=1353
  30. .88=1483
  31. .888=1490
  32. .8888=1507
  33. .9=1476
  34. .99=1633
  35. .999=1655
  36. .9999=1656
复制代码

作者: batman    时间: 2011-4-23 02:04

本帖最后由 batman 于 2011-4-23 02:06 编辑

上面的三次数据测试基本可以证明:

    cmd对变量的储存和读取是遵循其默认的序列机制的,即cmd会将当前设置的变量+=号+值的所有字符放在

一起进行正序排列(ansi编码序列),排前的字符放入内存中靠前的地址,而将排后的字符放入内存中靠后的地

址。然后在后面的读取寻址中则是一个遍历的过程,即首先查找比对存入最前地址中的字符,如果相符则取出

,如不相符则对下一个地址进行比对,一直到找到完全的匹配。也就是说当内存中储存的变量越多时,而要取

出后面地址中储存的变量所要消耗的时间越长(如在前面地址中则无明显时间的变化),也就是说当出现大量

变量时,对于排序在前的变量在程序中的运算影响并不是很大,而对排序在后的变量在程序中的运算这个影响

将是巨大的。由此可见cmd无论从哪个方面都逃不开其根本的序列机制(字符的ansi编码序列)。
作者: hanyeguxing    时间: 2011-4-23 03:04

本帖最后由 hanyeguxing 于 2011-4-23 09:03 编辑

环境变量名是按字符逐字顺序排列的,例如2排在17的后面,下面两图是系统环境变量在内存中的存储
.
[attach]3757[/attach]
[attach]3758[/attach]
.
为什么要用 set /a 呢?set /a 能代表单纯的变量读写吗?使用 set a=%#10000% 这样的形式更合适吧?
测试代码:
  1. @echo off
  2. rem set n=10000 1 39999
  3. rem set n=39999 -1 10000
  4. echo %time%开始%n%
  5. for /l %%a in (%n%) do set #%%a=1
  6. echo %time%赋值完成
  7. for /l %%a in (%n%) do set a=%#10000%
  8. echo %time%第一批完成
  9. for /l %%a in (%n%) do set a=%#39999%
  10. echo %time%第二批完成
  11. pause
复制代码
第一次set n=39999 -1 10000倒序两个差距千分之1.3,第二次set n=10000 1 39999正序(没关第一个cmd)差距千分之2.17
.
[attach]3759[/attach]
.
怎么就没得出有那么大的差距呢?
.
[attach]3761[/attach]
.
命令解释器在读写变量时是按照变量名逐个顺序匹配的的。
1,变量名顺序,以上图本地化变量组为例。
2,特例。特殊情况也是存在的,例如变量 %COMSPEC% 就单独排在整个变量的最前面。这个破规则从MS-DOS时代就开始了,因为那个时候他是核心,到今天的系统依然作如此保留。
3,两个变量之间由 00 空字符分隔
4,逐字匹配。先以变量名的第一个字符在本地变量存储空间顺序匹配变量名的第一个字符;如果匹配到,则继续在以第一字符开头的区间内继续匹配第二个字符,如果没有就直接返回为空...以此类推,而不是遍历整个变量存储空间。
这样做的好处是大大加快变量的读写速度。
如果变量存储的顺序是按照赋值的先后排列的话,那么每次读取都需要完整遍历一次整个本地变量空间,那是很没效率的,微软也不会这么设计。
作者: plp626    时间: 2011-4-23 08:03

为什么要用 set /a 呢?set /a 能代表单纯的变量读写吗?
hanyeguxing 发表于 2011-4-23 03:04


你考虑的也挺全面的呵。。。,
作者: plp626    时间: 2011-4-23 08:06

  1. @echo off
  2. :: loop值不能过小(大于2000即可),便于看出差异
  3. set/a loop=6000
  4. set/a p=500
  5. set/a num=loop/p
  6. :: 测试变量为已储存变量数的5百分之一,
  7. :: 能看出均匀增加,但测试结果和loop值的千分之一有直接关系
  8. echo “垃圾变量名”中【含有】待赋值变量的测试
  9. for /l %%a in (1,1,%loop%) do set _%%a=1
  10. setlocal EnableDelayedExpansion
  11. for /l %%z in (1 1 %num%)do (set/a tp=tt,tt=0
  12. set t1=!time!
  13. for /l %%a in (1 1 %loop%)do set _%%z=1
  14. set t2=!time!
  15. set/a tt=1!t2:~-5,2!!t2:~-2!-1!t1:~-5,2!!t1:~-2!,tt+="-6000*(tt>>31)"
  16. set /a cc=tt-tp
  17. echo  执行%loop%次set _%%z=1耗时:!tt!,阶差:!cc!
  18. )
  19. pause
复制代码
运行结果:
  1. “垃圾变量名”中【含有】待赋值变量的测试
  2. 执行6000次set _1=1耗时:125,阶差:125
  3. 执行6000次set _2=1耗时:148,阶差:23
  4. 执行6000次set _3=1耗时:183,阶差:35
  5. 执行6000次set _4=1耗时:215,阶差:32
  6. 执行6000次set _5=1耗时:252,阶差:37
  7. 执行6000次set _6=1耗时:286,阶差:34
  8. 执行6000次set _7=1耗时:289,阶差:3
  9. 执行6000次set _8=1耗时:290,阶差:1
  10. 执行6000次set _9=1耗时:291,阶差:1
  11. 执行6000次set _10=1耗时:122,阶差:-169
  12. 执行6000次set _11=1耗时:125,阶差:3
  13. 执行6000次set _12=1耗时:128,阶差:3
复制代码

作者: plp626    时间: 2011-4-23 08:15

昨晚的测试代码含有call,运行后比较不出来,但把语句放在一个复合语句里却可以,

原因分析
1、垃圾变量设定太少;
2、call的堆栈也占用空间;
作者: plp626    时间: 2011-4-23 08:18

本帖最后由 plp626 于 2011-4-23 08:25 编辑
  1. :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  2. :: 这个代码可以根据设定,比较出以下因素:
  3. :: 1、 垃圾变量数
  4. :: 2、 set赋值方式(是否带/a参数)
  5. :: 3、 待赋值变量是否已经包含在垃圾变量集合内
  6. :: 对赋值操作耗时的影响
  7. :::::::::::::::::::::::::::::::::::::::::::::::::::::::
  8. @echo off&setlocal EnableDelayedExpansion
  9. :: loop值不能过小,便于比较出差异
  10. set/a loop=6000
  11. set/a p=500
  12. set/a num=loop/p
  13. :: 测试变量为已储存变量数的5百分之一
  14. :: 能看出均匀增加,但测试结果和loop值的千分之一有直接关系
  15. set "pre1=_"      :: 垃圾变量前缀
  16. set "pre2=_"      :: 待赋值变量前缀
  17. set "med=set "  :: 赋值方式
  18. ======================%测试开始%=========================
  19. if "%pre1%"=="%pre2%" (
  20. echo “垃圾变量名”中【含有】待赋值变量的测试
  21. ) else (
  22. echo “垃圾变量名”中【不含有】待赋值变量的测试
  23. )
  24. :: 生成“垃圾变量”
  25. for /l %%a in (1,1,%loop%) do set %pre1%%%a=1
  26. :: 测试赋值耗时变化
  27. for /l %%z in (1 1 %num%)do (set/a tp=tt,tt=0
  28. set t1=!time!
  29. for /l %%a in (1 1 %loop%)do %med% %pre2%%%z=1
  30. set t2=!time!
  31. set/a tt=1!t2:~-5,2!!t2:~-2!-1!t1:~-5,2!!t1:~-2!,tt+="-6000*(tt>>31)"
  32. set /a cc=tt-tp
  33. echo  执行%loop%次%med% %pre2%%%z=1耗时:!tt!,阶差:!cc!
  34. )
  35. pause
复制代码

作者: caruko    时间: 2011-4-23 08:30

57# hanyeguxing


从你的代码来看,假如CMD预处理时只取一次变量因为是%%而不是!!,而其中变量 a 是排在最后的,所以每次搜索到"a", 也必然搜索到了前面的#数字变量。
那么花的时候必然相同。
或许使用!!试一试会不一样。
作者: plp626    时间: 2011-4-23 08:31

设定
set loop=6000
set "pre1=_"      :: 垃圾变量前缀
set "pre2=_"      :: 待赋值变量前缀
set "med=set "    :: 赋值方式
  1. @echo off&setlocal EnableDelayedExpansion
  2. :: loop值也是垃圾变量的数量,不能过小,便于比较出差异
  3. set/a loop=6000
  4. set/a p=500
  5. set/a num=loop/p
  6. set "pre1=_"      :: 垃圾变量前缀
  7. set "pre2=_"      :: 待赋值变量前缀
  8. set "med=set "    :: 赋值方式
  9. ======================%测试开始%=========================
  10. echo 待测试变量数占垃圾变量数(%loop%个)的比:1/%p%
  11. if "%pre1%"=="%pre2%" (
  12.         echo “垃圾变量名”中【含有】待赋值变量的测试
  13. ) else (
  14.         echo “垃圾变量名”中【不含有】待赋值变量的测试
  15. )
  16. echo ====================================
  17. :: 生成“垃圾变量”
  18. for /l %%a in (1,1,%loop%) do set %pre1%%%a=1
  19. :: 测试赋值耗时变化
  20. for /l %%z in (1 1 %num%)do (set/a tp=tt,tt=0
  21.         set t1=!time!
  22.         for /l %%a in (1 1 %loop%)do %med% %pre2%%%z=1
  23.         set t2=!time!
  24. set/a tt=1!t2:~-5,2!!t2:~-2!-1!t1:~-5,2!!t1:~-2!,tt+="-6000*(tt>>31)"
  25. set /a cc=tt-tp
  26. echo  执行%loop%次%med% %pre2%%%z=1耗时:!tt!,阶差:!cc!
  27. )
  28. pause
复制代码
输出:
  1. 待测试变量数占垃圾变量数(6000个)的比:1/500
  2. “垃圾变量名”中【含有】待赋值变量的测试
  3. =====================================
  4. 执行6000次set _1=1耗时:116,阶差:116
  5. 执行6000次set _2=1耗时:152,阶差:36
  6. 执行6000次set _3=1耗时:185,阶差:33
  7. 执行6000次set _4=1耗时:221,阶差:36
  8. 执行6000次set _5=1耗时:250,阶差:29
  9. 执行6000次set _6=1耗时:286,阶差:36
  10. 执行6000次set _7=1耗时:289,阶差:3
  11. 执行6000次set _8=1耗时:294,阶差:5
  12. 执行6000次set _9=1耗时:299,阶差:5
  13. 执行6000次set _10=1耗时:117,阶差:-182
  14. 执行6000次set _11=1耗时:118,阶差:1
  15. 执行6000次set _12=1耗时:122,阶差:4
复制代码

作者: plp626    时间: 2011-4-23 08:36

设定
set loop=6000
set "pre1=_"      :: 垃圾变量前缀
set "pre2=_"      :: 待赋值变量前缀
set "med=set/a"    :: 赋值方式
  1. @echo off&setlocal EnableDelayedExpansion
  2. :: loop值也是垃圾变量的数量,不能过小,便于比较出差异
  3. set/a loop=6000
  4. set/a p=500
  5. set/a num=loop/p
  6. set "pre1=_"      :: 垃圾变量前缀
  7. set "pre2=_"      :: 待赋值变量前缀
  8. set "med=set "    :: 赋值方式
  9. ======================%测试开始%=========================
  10. echo 待测试变量数占垃圾变量数(%loop%个)的比:1/%p%
  11. if "%pre1%"=="%pre2%" (
  12.         echo “垃圾变量名”中【含有】待赋值变量的测试
  13. ) else (
  14.         echo “垃圾变量名”中【不含有】待赋值变量的测试
  15. )
  16. echo ====================================
  17. :: 生成“垃圾变量”
  18. for /l %%a in (1,1,%loop%) do set %pre1%%%a=1
  19. :: 测试赋值耗时变化
  20. for /l %%z in (1 1 %num%)do (set/a tp=tt,tt=0
  21.         set t1=!time!
  22.         for /l %%a in (1 1 %loop%)do %med% %pre2%%%z=1
  23.         set t2=!time!
  24. set/a tt=1!t2:~-5,2!!t2:~-2!-1!t1:~-5,2!!t1:~-2!,tt+="-6000*(tt>>31)"
  25. set /a cc=tt-tp
  26. echo  执行%loop%次%med% %pre2%%%z=1耗时:!tt!,阶差:!cc!
  27. )
  28. pause
复制代码
输出:
  1. 待测试变量数占垃圾变量数(6000个)的比:1/500
  2. “垃圾变量名”中【含有】待赋值变量的测试
  3. ====================================
  4. 执行6000次set/a _1=1耗时:130,阶差:130
  5. 执行6000次set/a _2=1耗时:223,阶差:93
  6. 执行6000次set/a _3=1耗时:320,阶差:97
  7. 执行6000次set/a _4=1耗时:413,阶差:93
  8. 执行6000次set/a _5=1耗时:516,阶差:103
  9. 执行6000次set/a _6=1耗时:631,阶差:115
  10. 执行6000次set/a _7=1耗时:646,阶差:15
  11. 执行6000次set/a _8=1耗时:637,阶差:-9
  12. 执行6000次set/a _9=1耗时:648,阶差:11
  13. 执行6000次set/a _10=1耗时:124,阶差:-524
  14. 执行6000次set/a _11=1耗时:139,阶差:15
  15. 执行6000次set/a _12=1耗时:147,阶差:8
  16. 请按任意键继续. . .
复制代码

作者: caruko    时间: 2011-4-23 08:37

  1. @echo off&setlocal enabledelayedexpansion
  2. set n=10000 1 39999
  3. rem set n=39999 -1 10000
  4. echo %time%开始%n%
  5. for /l %%a in (%n%) do set #%%a=1
  6. echo %time%赋值完成
  7. for /l %%a in (%n%) do set a=!#10000!
  8. echo %time%第一批完成
  9. for /l %%a in (%n%) do set a=!#39999!
  10. echo %time%第二批完成
  11. pause
复制代码
电脑太废...看结果
  1. 8:32:28.14开始10000 1 39999
  2. 8:33:11.32赋值完成
  3. 8:34:32.75第一批完成
  4. 8:36:49.43第二批完成
  5. 请按任意键继续. . .
复制代码

作者: caruko    时间: 2011-4-23 08:39

本帖最后由 caruko 于 2011-4-23 08:57 编辑

如果以上成立的话,那么以后写代码,在大量变量存在的空间,能不用!!就尽量不用了。

还有,hanyeguxin 如果你的逐字搜索来说,那么 set _10000 比set _9 快 无法解释。
而且set _x 比所有 _number 慢也无法解释。

逐字搜索,需要类似数据库映射表,这样才会加快速度。但 set _10000 比 set _9 快很多,可见不成立。
而如果搜索一个单字符,也是遍历进行的,那么假如变量字符很长,需要搜索的次数反而应该大大增加吧?
作者: plp626    时间: 2011-4-23 08:46

设定
set loop=10000
set "pre1=_"      :: 垃圾变量前缀
set "pre2=_"      :: 待赋值变量前缀
set "med=set/a"    :: 赋值方式
  1. @echo off&setlocal EnableDelayedExpansion
  2. :: loop值也是垃圾变量的数量,不能过小,便于比较出差异
  3. set/a loop=10000
  4. set/a p=500
  5. set/a num=loop/p
  6. set "pre1=_"      :: 垃圾变量前缀
  7. set "pre2=_"      :: 待赋值变量前缀
  8. set "med=set "    :: 赋值方式
  9. ======================%测试开始%=========================
  10. echo 待测试变量数占垃圾变量数(%loop%个)的比:1/%p%
  11. if "%pre1%"=="%pre2%" (
  12.         echo “垃圾变量名”中【含有】待赋值变量的测试
  13. ) else (
  14.         echo “垃圾变量名”中【不含有】待赋值变量的测试
  15. )
  16. echo ====================================
  17. :: 生成“垃圾变量”
  18. for /l %%a in (1,1,%loop%) do set %pre1%%%a=1
  19. :: 测试赋值耗时变化
  20. for /l %%z in (1 1 %num%)do (set/a tp=tt,tt=0
  21.         set t1=!time!
  22.         for /l %%a in (1 1 %loop%)do %med% %pre2%%%z=1
  23.         set t2=!time!
  24. set/a tt=1!t2:~-5,2!!t2:~-2!-1!t1:~-5,2!!t1:~-2!,tt+="-6000*(tt>>31)"
  25. set /a cc=tt-tp
  26. echo  执行%loop%次%med% %pre2%%%z=1耗时:!tt!,阶差:!cc!
  27. )
  28. pause
复制代码
  1. 待测试变量数占垃圾变量数(10000个)的比:1/500
  2. “垃圾变量名”中【含有】待赋值变量的测试
  3. ====================================
  4. 执行10000次set _1=1耗时:328,阶差:328 // 注释,第一个阶差不做参考
  5. 执行10000次set _2=1耗时:359,阶差:31
  6. 执行10000次set _3=1耗时:432,阶差:73
  7. 执行10000次set _4=1耗时:491,阶差:59
  8. 执行10000次set _5=1耗时:550,阶差:59
  9. 执行10000次set _6=1耗时:603,阶差:53
  10. 执行10000次set _7=1耗时:653,阶差:50
  11. 执行10000次set _8=1耗时:717,阶差:64
  12. 执行10000次set _9=1耗时:775,阶差:58
  13. 执行10000次set _10=1耗时:342,阶差:-433 // 注意垃圾变量数的千分之一
  14. 执行10000次set _11=1耗时:339,阶差:-3
  15. 执行10000次set _12=1耗时:323,阶差:-16
  16. 执行10000次set _13=1耗时:330,阶差:7
  17. 执行10000次set _14=1耗时:348,阶差:18
  18. 执行10000次set _15=1耗时:346,阶差:-2
  19. 执行10000次set _16=1耗时:348,阶差:2
  20. 执行10000次set _17=1耗时:380,阶差:32
  21. 执行10000次set _18=1耗时:384,阶差:4
  22. 执行10000次set _19=1耗时:400,阶差:16
  23. 执行10000次set _20=1耗时:384,阶差:-16
  24. 请按任意键继续. . .
复制代码

作者: plp626    时间: 2011-4-23 08:52

有很多因素影响赋值操作的
作者: CrLf    时间: 2011-4-23 08:54

@echo off&setlocal enabledelayedexpansion
set n=10000 1 39999
rem set n=39999 -1 10000
echo %time%开始%n%
for /l %%a in (%n%) do set #%%a=1
echo %time%赋值完成
for /l %%a in (%n%) do set a=!#1 ...
caruko 发表于 2011-4-23 08:37

%和!在for中的耗时明显不一样,一个语块只解释一次%str%,而!str!的解释次数与相应语句的执行次数成正比,假如在六千次循环中分别用%str%和!str!测试,!str!会比%str%多出5999次花在预处理中的耗时
作者: hanyeguxing    时间: 2011-4-23 09:01

奇怪,你们为什么就不用debug直接查询本地化变量在内存中的存储呢,很容易的事
作者: caruko    时间: 2011-4-23 09:02

69# zm900612


不错,因为 set /a =%#39999% 这一句,在预处理 a 变量时,搜索指针就已经经过了 #39999 变量,一次预处理得到了a,#399992个值,所以实际耗时就是一次取值 "a "的耗时。
所以hanyeguxin的代码才没有测试出差距。
作者: CrLf    时间: 2011-4-23 09:05

奇怪,你们为什么就不用debug直接查询本地化变量在内存中的存储呢,很容易的事
hanyeguxing 发表于 2011-4-23 09:01

门外汉表示没学过...麻烦您教教呗
作者: CrLf    时间: 2011-4-23 09:15

看了plp626老兄几次测试数据,百思不得其解为什么只要循环到变量名为_1n的时候,阶差会突然间低得可怜,居然还有负的...和_1完全不在一个档次上,这是什么原因呢?
作者: plp626    时间: 2011-4-23 09:15

本帖最后由 plp626 于 2011-4-23 10:35 编辑

垃圾变量名集合 不含有待赋值变量 与 含有待赋值变量 的比较

上面那个代码设定
set loop=4500
set p=150
set "pre1=_"      :: 垃圾变量前缀
set "pre2=#"      :: 待赋值变量前缀
set "med=set"    :: 赋值方式

set loop=4500
set p=150
set "pre1=_"      :: 垃圾变量前缀
set "pre2=_"      :: 待赋值变量前缀
set "med=set"    :: 赋值方式
的输出:分别为
  1. 待测试变量数占垃圾变量数(4500个)的比:1/150
  2. “垃圾变量名”中【不含有】待赋值变量的测试
  3. ====================================
  4. 执行4500次set _1=1耗时:161,阶差:161 // 第一个阶差不做参考
  5. 执行4500次set _2=1耗时:161,阶差:0
  6. 执行4500次set _3=1耗时:163,阶差:2
  7. 执行4500次set _4=1耗时:162,阶差:-1
  8. 执行4500次set _5=1耗时:163,阶差:1
  9. 执行4500次set _6=1耗时:161,阶差:-2
  10. 执行4500次set _7=1耗时:161,阶差:0
  11. 执行4500次set _8=1耗时:161,阶差:0
  12. 执行4500次set _9=1耗时:164,阶差:3
  13. 执行4500次set _10=1耗时:161,阶差:-3
  14. 执行4500次set _11=1耗时:161,阶差:0
  15. 执行4500次set _12=1耗时:161,阶差:0
  16. 执行4500次set _13=1耗时:161,阶差:0
  17. 执行4500次set _14=1耗时:159,阶差:-2
  18. 执行4500次set _15=1耗时:159,阶差:0
  19. 执行4500次set _16=1耗时:162,阶差:3
  20. 执行4500次set _17=1耗时:163,阶差:1
  21. 执行4500次set _18=1耗时:160,阶差:-3
  22. 执行4500次set _19=1耗时:161,阶差:1
  23. 执行4500次set _20=1耗时:162,阶差:1
  24. 执行4500次set _21=1耗时:161,阶差:-1
  25. 执行4500次set _22=1耗时:161,阶差:0
  26. 执行4500次set _23=1耗时:161,阶差:0
  27. 执行4500次set _24=1耗时:161,阶差:0
  28. 执行4500次set _25=1耗时:161,阶差:0
  29. 执行4500次set _26=1耗时:161,阶差:0
  30. 执行4500次set _27=1耗时:162,阶差:1
  31. 执行4500次set _28=1耗时:164,阶差:2
  32. 执行4500次set _29=1耗时:163,阶差:-1
  33. 执行4500次set _30=1耗时:162,阶差:-1
  34. 请按任意键继续. . .
复制代码
::::::::::::::::::::::::::::::::::::::::::::::::
  1. 待测试变量数占垃圾变量数(4500个)的比:1/150
  2. “垃圾变量名”中【含有】待赋值变量的测试
  3. ====================================
  4. 执行4500次set _1=1耗时:67,阶差:67
  5. 执行4500次set _2=1耗时:91,阶差:24
  6. 执行4500次set _3=1耗时:116,阶差:25
  7. 执行4500次set _4=1耗时:139,阶差:23
  8. 执行4500次set _5=1耗时:155,阶差:16
  9. 执行4500次set _6=1耗时:155,阶差:0  // 略微变化
  10. 执行4500次set _7=1耗时:158,阶差:3
  11. 执行4500次set _8=1耗时:162,阶差:4
  12. 执行4500次set _9=1耗时:164,阶差:2
  13. 执行4500次set _10=1耗时:68,阶差:-96 // 显著变化
  14. 执行4500次set _11=1耗时:73,阶差:5
  15. 执行4500次set _12=1耗时:72,阶差:-1
  16. 执行4500次set _13=1耗时:73,阶差:1
  17. 执行4500次set _14=1耗时:75,阶差:2
  18. 执行4500次set _15=1耗时:78,阶差:3
  19. 执行4500次set _16=1耗时:79,阶差:1
  20. 执行4500次set _17=1耗时:83,阶差:4
  21. 执行4500次set _18=1耗时:84,阶差:1
  22. 执行4500次set _19=1耗时:88,阶差:4
  23. 执行4500次set _20=1耗时:91,阶差:3
  24. 执行4500次set _21=1耗时:94,阶差:3
  25. 执行4500次set _22=1耗时:95,阶差:1
  26. 执行4500次set _23=1耗时:98,阶差:3
  27. 执行4500次set _24=1耗时:100,阶差:2
  28. 执行4500次set _25=1耗时:105,阶差:5
  29. 执行4500次set _26=1耗时:106,阶差:1
  30. 执行4500次set _27=1耗时:108,阶差:2
  31. 执行4500次set _28=1耗时:111,阶差:3
  32. 执行4500次set _29=1耗时:112,阶差:1
  33. 执行4500次set _30=1耗时:116,阶差:4
  34. 请按任意键继续. . .
复制代码

作者: hanyeguxing    时间: 2011-4-23 09:15

本帖最后由 hanyeguxing 于 2011-4-23 09:23 编辑

  系统每运行一次命令解释器都要生成一个256字节的内存块——程序段前缀PSP(Program Segment Prefix),它包含了一些比较重要的信息,如内存单元、矢量、命令行上键入的文件名、命令行本身(用户运行程序的命令行的拷贝)、各种保留缓冲器存储地点的信息。只要涉及环境,那些存储地点之一就会保存系统环境地址。

  打开命令解释器,运行 for /l %a in (1,1,100) do set "#%a=%a" ,设置这些变量,接着运行 DEBUG

  在DEBUG命令状态下查看环境,首先查看偏移量2C处的两个字节:

  -D2CL2
   0B2DA:0020  66 05

  数据66 05是在某系统上得到的计算机环境的段地址值(操作时应以实际显示数为准),把这两对数以相反的顺序,并在后面跟上偏移量0,就能在DEBUG命令上显示系统环境。

  -D0566:0

[attach]3762[/attach]
作者: CrLf    时间: 2011-4-23 09:24

75# hanyeguxing

原来如此,学习了。追问一下:“在DEBUG命令状态下查看环境,首先查看偏移量2C处的两个字节:”这里的2C是怎么的出来的呢?
作者: hanyeguxing    时间: 2011-4-23 09:25

从内存中可以看出,几乎和set 显示的一样,唯一的差别就是%COMSPEC%永远在内存的最前面。。。
作者: batman    时间: 2011-4-23 09:29

hanyeguxing的debug变量储存展示已经力证了变量存储是按照变量名+=号+值全字符的ansi序列先后进行的,为什么还要强调变量的读取又不是按照这一基本序列机制呢?
作者: batman    时间: 2011-4-23 09:29

hanyeguxing的debug变量储存展示已经力证了变量存储是按照变量名+=号+值全字符的ansi序列先后进行的,为什么还要强调变量的读取又不是按照这一基本序列机制呢?
作者: hanyeguxing    时间: 2011-4-23 09:32

77# zm900612


从当年MS-DOS开始,这里就标记环境变量的段地址值,到windows下都没变过。。。
作者: caruko    时间: 2011-4-23 09:42

果然是 变量名+值 都一起以字符存储的。
难怪 set a=1 比 set /a a=1要快。

这种存储法,自由度不错,某种程度上方便了用户,但效率就低了。
作者: plp626    时间: 2011-4-23 09:52

有一个结论对指导我们提高效率还有一定意义的:

垃圾变量名集合包含待赋值变量名时,待赋值变量的赋值耗时
明显少于
垃圾变量名集合不包含待赋值变量名时,对 待赋值变量的赋值耗时
作者: hanyeguxing    时间: 2011-4-23 10:20

80# batman


1,用来说明微软为什么使用全字符ANSI来排列变量名。
2,命令解释器进内存中的环境变量区间是如何匹配的,是拿着变量名一个一个的遍历所有变量名来比较,还是按着ANSI顺序逐字比较变量名。
作者: batman    时间: 2011-4-23 10:35

本帖最后由 batman 于 2011-4-23 11:19 编辑

加入debug的测试
代码:
  1. @echo off&setlocal enabledelayedexpansion
  2. call :lp 1 10 random
  3. call :lp 11 100 random
  4. del /q db
  5. goto :eof
  6. :lp
  7. for /l %%a in (%1,1,%2) do set "_%%a=!%3!"
  8. (echo d2cl2
  9. echo q
  10. echo,
  11. )>db
  12. (for /f "skip=1 tokens=2,3" %%a in ('debug^<db') do (
  13.      echo d%%b%%a:0380&echo q&echo,
  14. ))>tem
  15. del /q db&ren tem db
  16. debug<db>%1.txt
  17. start %1.txt
复制代码
1.txt
  1. -d0D96:0380
  2. 0D96:0380  00 5F 31 3D 36 31 33 35-00 5F 31 30 3D 32 38 36   ._1=6135._10=286
  3. 0D96:0390  31 32 00 5F 32 3D 32 39-36 38 30 00 5F 33 3D 32   12._2=29680._3=2
  4. 0D96:03A0  33 30 36 39 00 5F 34 3D-33 31 32 32 37 00 5F 35   3069._4=31227._5
  5. 0D96:03B0  3D 38 34 37 37 00 5F 36-3D 32 33 39 38 38 00 5F   =8477._6=23988._
  6. 0D96:03C0  37 3D 33 32 36 31 36 00-5F 38 3D 32 39 35 38 00   7=32616._8=2958.
  7. 0D96:03D0  5F 39 3D 32 37 37 32 31-00 42 4C 41 53 54 45 52   _9=27721.BLASTER
  8. 0D96:03E0  3D 41 32 32 30 20 49 35-20 44 31 20 50 33 33 30   =A220 I5 D1 P330
  9. 0D96:03F0  20 54 33 00 00 01 00 43-3A 5C 57 49 4E 44 4F 57    T3....C:\WINDOW
  10. -q
复制代码
11.txt
  1. -d0E0D:0380
  2. 0E0D:0380  00 5F 31 3D 36 31 33 35-00 5F 31 30 3D 32 38 36   ._1=6135._10=286
  3. 0E0D:0390  31 32 00 5F 31 30 30 3D-31 34 36 31 34 00 5F 31   12._100=14614._1
  4. 0E0D:03A0  31 3D 35 33 30 37 00 5F-31 32 3D 33 30 32 38 37   1=5307._12=30287
  5. 0E0D:03B0  00 5F 31 33 3D 31 30 35-31 00 5F 31 34 3D 35 39   ._13=1051._14=59
  6. 0E0D:03C0  36 37 00 5F 31 35 3D 32-37 34 37 38 00 5F 31 36   67._15=27478._16
  7. 0E0D:03D0  3D 31 35 33 37 33 00 5F-31 37 3D 31 37 30 31 33   =15373._17=17013
  8. 0E0D:03E0  00 5F 31 38 3D 32 36 38-31 37 00 5F 31 39 3D 38   ._18=26817._19=8
  9. 0E0D:03F0  31 33 31 00 5F 32 3D 32-39 36 38 30 00 5F 32 30   131._2=29680._20
  10. -q
复制代码
由此可见cmd在变量有变化的情况下会重新分配内存的储存地址,1.txt和11.txt中开始的地址分别为0D96:0380和0E0D:0380,而这一存储过程是按变量名+=号+值的全字符(实际上就是变量名,因为cmd是不会允许有同名变量的存在的)的ansi序列先后进行的。
作者: batman    时间: 2011-4-23 11:12

本帖最后由 batman 于 2011-4-23 11:21 编辑

再次测试(测试前猜想变量减少时,变量的内存存储开始地址不会改变)
代码:
  1. @echo off&setlocal enabledelayedexpansion
  2. del /q db-*.txt
  3. call :lp 1 100 random
  4. call :lp 10 20
  5. del /q db
  6. goto :eof
  7. :lp
  8. for /l %%a in (%1,1,%2) do set "_%%a=!%3!"
  9. (echo d2cl2
  10. echo q
  11. echo,
  12. )>db
  13. (for /f "skip=1 tokens=2,3" %%a in ('debug^<db') do (
  14.      echo d%%b%%a:0380&echo q&echo,
  15. ))>tem
  16. del /q db&ren tem db
  17. debug<db>db-%1.txt
  18. start db-%1.txt
复制代码
db-1.txt
  1. -d0DCC:0380
  2. 0DCC:0380  00 5F 31 3D 31 34 37 36-32 00 5F 31 30 3D 35 35   ._1=14762._10=55
  3. 0DCC:0390  37 37 00 5F 31 30 30 3D-32 31 31 35 33 00 5F 31   77._100=21153._1
  4. 0DCC:03A0  31 3D 37 37 35 35 00 5F-31 32 3D 31 35 35 38 36   1=7755._12=15586
  5. 0DCC:03B0  00 5F 31 33 3D 31 30 38-31 00 5F 31 34 3D 38 38   ._13=1081._14=88
  6. 0DCC:03C0  34 39 00 5F 31 35 3D 34-34 35 31 00 5F 31 36 3D   49._15=4451._16=
  7. 0DCC:03D0  31 32 39 34 35 00 5F 31-37 3D 32 35 37 33 35 00   12945._17=25735.
  8. 0DCC:03E0  5F 31 38 3D 31 33 30 31-31 00 5F 31 39 3D 32 37   _18=13011._19=27
  9. 0DCC:03F0  39 39 35 00 5F 32 3D 31-37 31 31 33 00 5F 32 30   995._2=17113._20
  10. -q
复制代码
db-10.txt
  1. -d0DCC:0380
  2. 0DCC:0380  00 5F 31 3D 31 34 37 36-32 00 5F 31 30 30 3D 32   ._1=14762._100=2
  3. 0DCC:0390  31 31 35 33 00 5F 32 3D-31 37 31 31 33 00 5F 32   1153._2=17113._2
  4. 0DCC:03A0  31 3D 32 37 37 36 30 00-5F 32 32 3D 33 37 36 35   1=27760._22=3765
  5. 0DCC:03B0  00 5F 32 33 3D 31 33 30-37 35 00 5F 32 34 3D 37   ._23=13075._24=7
  6. 0DCC:03C0  35 38 38 00 5F 32 35 3D-31 31 31 30 36 00 5F 32   588._25=11106._2
  7. 0DCC:03D0  36 3D 32 33 36 38 35 00-5F 32 37 3D 31 32 37 38   6=23685._27=1278
  8. 0DCC:03E0  39 00 5F 32 38 3D 32 30-33 30 31 00 5F 32 39 3D   9._28=20301._29=
  9. 0DCC:03F0  32 31 39 35 31 00 5F 33-3D 31 35 32 32 00 5F 33   21951._3=1522._3
  10. -q
复制代码
可见当变量减少时(部分变量被清空),其实就是对已有变量进行读取操作时,内存确实不会重新洗牌。
作者: applba    时间: 2011-4-23 11:16

8# plp626


当然了,set /a 要把字符串类型转换成数字类型。这里是快了,如果后面的这个变量要参与数学计算的话,通过set设置的字符串还是需要再次转换成数字类型的,这样时间就扯平了。变量一般只设置一次,但是这个变量可能参与多次数学计算的话,很明显set /a  设置的数字类型将更有优势。
作者: CrLf    时间: 2011-4-23 11:21

89# applba


批处理存储在内存的变量是否有数据类型之分呢?这又是一个分歧点,有得闹了
作者: CrLf    时间: 2011-4-23 13:05

人呢人呢?
这种版主级的舌战正是我所乐见的,向往了这么久,难得碰到一两次,怎么才讨论了一晚上+一上午就停火了...
问题还没解决呐...疑点还很多,举几个例子:
一、究竟是一张表还是N张表?子疑点:
1、系统变量与用户变量的关系,当用户变量覆盖系统变量时,系统变量去哪了?
2、setlocal后的变量是否和setlocal前的变量放在一张表里,并且一起按ansi排序呢?
二、变量存储时是否区分数据类型?
三、只感觉还有些问题迷迷糊糊,但没能抓住思绪,因此下文待续...
作者: cjiabing    时间: 2011-4-23 13:14

本帖最后由 cjiabing 于 2011-4-23 13:15 编辑
80# batman


1,用来说明微软为什么使用全字符ANSI来排列变量名。
2,命令解释器进内存中的环境变量区间是如何匹配的,是拿着变量名一个一个的遍历所有变量名来比较,还是按着ANSI顺序逐字比较变量名。
hanyeguxing 发表于 2011-4-23 10:20

      还是兄科学发展观学得好,不懂他们鼓捣出什么“真理”出来没有!~
      既然是“变量机制”,至少得出个有现实意义的结论,好指导我们平时怎么设置变量。
      比如用 !var! 还是用 %var% ,用不同的变量还是用同一个变量,用%abc% 顺序的,还是%zijizuodepichulibianliangjizhi%冗长颠倒的变量,用变量还是用临时文件,用多个不同变量,还是用同一个变量……
      再问深一点的:变量是怎么来的?它的作用是什么?它的运行机制是什么?它在内存中是怎么样工作的?变量是如何赋值和如何取消的?微软是怎么解释的?微软是怎么使用变量的?变量与什么有关?变量的极限是什么?有没有最佳和最差变量?有没有系统内部通用变量?怎么区分和联系各种变量?批处理的变量是否可以超出cmd.exe?计算机编码和进制对变量有什么影响?变量在解释器中如何解释?……
作者: batman    时间: 2011-4-23 13:32

本帖最后由 batman 于 2011-4-23 13:44 编辑

还是测试说明问题,但也带来了新问题。。。。
代码:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /l %%a in (1,1,100) do set "d=!d!&echo d"
  3. call :lp _str a
  4. call :lp _str b setlocal endlocal
  5. call :lp _str s
  6. del /q db&pause
  7. goto :eof
  8. :lp
  9. %3
  10. if "%2" neq "s" set "%1=%2"
  11. (echo d2cl2
  12. echo q
  13. echo,
  14. )>db
  15. (for /f "skip=1 tokens=2,3" %%a in ('debug^<db') do (
  16.      echo d%%b%%a:0000%d%&echo q&echo,
  17. ))>tem
  18. del /q db&ren tem db
  19. for /f "delims=" %%a in ('debug^<db^|findstr /i "%1="') do echo %%a
  20. echo,
  21. %4
复制代码
结果:
  1. 0DBD:0640  5F 53 54 52 3D 61 00 42-4C 41 53 54 45 52 3D 41   _STR=a.BLASTER=A
  2. 0DBD:0640  5F 53 54 52 3D 62 00 42-4C 41 53 54 45 52 3D 41   _STR=b.BLASTER=A
  3. 0DBD:0640  5F 53 54 52 3D 61 00 42-4C 41 53 54 45 52 3D 41   _STR=a.BLASTER=A
  4. Press any key to continue . . .
复制代码
上面的测试表明,setlocal前以及setlcoal-endlocal中以及endlocal后_str变量在内存中储存的地址没有改变(至少是开始值没有改变)。同时说明了cmd中为什么变量名不分大小写,因为不管变量中的字符是不是大小写,内存中统一是以大写的字符储存的变量。但这里又有一个问题了,既然内存中只会储存一个名为_str的变量,那么endlocal后这个变量的值又是怎么恢复为前面的值的,难道还有一个临时储存变量的地方?
作者: batman    时间: 2011-4-23 13:55

测试系统变量:
  1. @echo off&setlocal enabledelayedexpansion
  2. for /l %%a in (1,1,100) do set "d=!d!&echo d"
  3. call :lp os
  4. call :lp os a setlocal endlocal
  5. call :lp os
  6. del /q db&pause
  7. goto :eof
  8. :lp
  9. %3
  10. if "%2" neq "" set "%1=%2"
  11. (echo d2cl2
  12. echo q
  13. echo,
  14. )>db
  15. (for /f "skip=1 tokens=2,3" %%a in ('debug^<db') do (
  16.      echo d%%b%%a:0000%d%&echo q&echo,
  17. ))>tem
  18. del /q db&ren tem db
  19. for /f "delims=" %%a in ('debug^<db^|findstr /i "%1="') do echo %%a
  20. echo,
  21. %4
复制代码
  1. 0DBD:03F0  52 4F 43 45 53 53 4F 52-53 3D 32 00 4F 53 3D 57   ROCESSORS=2.OS=W
  2. 0DBD:03F0  52 4F 43 45 53 53 4F 52-53 3D 32 00 4F 53 3D 61   ROCESSORS=2.OS=a
  3. 0DBD:03F0  52 4F 43 45 53 53 4F 52-53 3D 32 00 4F 53 3D 57   ROCESSORS=2.OS=W
  4. Press any key to continue . . .
复制代码
系统变量os在setlcoal-enlocal中去哪里了?,假如将os改成path,运行结果如下:
  1. 0DBD:0400  69 6E 64 6F 77 73 5F 4E-54 00 50 41 54 48 3D 43   indows_NT.PATH=C
  2. 'debug' is not recognized as an internal or external command,
  3. operable program or batch file.
  4. 'debug' is not recognized as an internal or external command,
  5. operable program or batch file.
  6. 0DBD:0400  69 6E 64 6F 77 73 5F 4E-54 00 50 41 54 48 3D 43   indows_NT.PATH=C
  7. Press any key to continue . . .
复制代码
在重新设定path后,debug命令变得无效。
作者: CrLf    时间: 2011-4-23 14:05

“在重新设定path后,debug命令变得无效。”
debug是外部命令,path变量一改变就找不到文件了
作者: plp626    时间: 2011-4-23 14:06

还是测试说明问题,但也带来了新问题。。。。
代码:
@echo off&setlocal enabledelayedexpansion
for /l %%a in (1,1,100) do set "d=!d!&echo d"
call :lp _str a
call :lp _str b setlocal endlocal
call : ...
batman 发表于 2011-4-23 13:32


有一点大家想过么?

call的话就有堆栈,有堆栈在内存中是否影响变量的赋值耗时?

昨晚我就用call,当然也和设置的变量数过少有关,得出的结论让我迷路。。。
作者: batman    时间: 2011-4-23 14:08

从大量的测试推断cmd.exe在每次运行时就会用以下命令向内存写入信息从而来设置环境变量:
  1. set "ALLUSERSPROFILE=C:\Documents and Settings\All Users"
  2. set "APPDATA=C:\Documents and Settings\Administrator\Application Data"
  3. set "CommonProgramFiles=C:\Program Files\Common Files"
  4. set "COMPUTERNAME=PC-200901071258"
  5. set "ComSpec=C:\WINDOWS\system32\cmd.exe"
  6. set "FP_NO_HOST_CHECK=NO"
  7. set "HOMEDRIVE=C:"
  8. set "HOMEPATH=\Documents and Settings\Administrator"
  9. set "LOGONSERVER=\\PC-200901071258"
  10. set "NUMBER_OF_PROCESSORS=2"
  11. set "OS=Windows_NT"
  12. set "Path=C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\Program Files\Common Files\Thunder Network\KanKan\Codecs;C:\Program Files\StormII\Codec;C:\Program Files\StormII"
  13. set "PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH"
  14. set "PROCESSOR_ARCHITECTURE=x86"
  15. set "PROCESSOR_IDENTIFIER=x86 Family 15 Model 107 Stepping 2, AuthenticAMD"
  16. set "PROCESSOR_LEVEL=15"
  17. set "PROCESSOR_REVISION=6b02"
  18. set "ProgramFiles=C:\Program Files"
  19. set "PROMPT=$P$G"
  20. set "SESSIONNAME=Console"
  21. set "SystemDrive=C:"
  22. set "SystemRoot=C:\WINDOWS"
  23. set "TEMP=C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp"
  24. set "TMP=C:\DOCUME~1\ADMINI~1\LOCALS~1\Temp"
  25. set "USERDOMAIN=PC-200901071258"
  26. set "USERNAME=Administrator"
  27. set "USERPROFILE=C:\Documents and Settings\Administrator"
  28. set "windir=C:\WINDOWS"
复制代码

作者: batman    时间: 2011-4-23 14:10

95# zm900612
我说的就是这个意思,只是没有说明罢了,呵呵。。。
作者: CrLf    时间: 2011-4-23 15:00

本帖最后由 zm900612 于 2011-4-23 15:05 编辑

99# plp626


我理解错了...而且初中高中的数学,我都没怎么听课,喜欢用自己的方法东拼西凑解题

98# batman


噢,怪不得,我还纳闷老兄怎么可能把这忘了


这话题越来越好玩了,斑竹们继续吵架,我是跑龙套的群众演员,等结果
作者: hanyeguxing    时间: 2011-4-24 04:35

本帖最后由 hanyeguxing 于 2011-4-24 06:18 编辑

运行批处理时,命令解释器就复制了系统环境变量进入本地,这些系统变量也就本地化了,而 set 的都是本地,所以用set 也只改变本地环境变量
打个比方,系统变量为其他位置的一个源文件,系统变量本地化就像为他在当前目录创建快捷方式,快捷方式可以随意访问源文件。
而 set 这些本地化的系统变量就相当于修改了快捷方式名称,如何修改都不会改变源文件的名称
作者: CrLf    时间: 2011-4-24 11:45

那清空了本地化操作之后呢?命令解释器是再次复制系统变量进入本地吗?
还有setlocal呢?前后都是本地化操作,为什么endlocal之后能还原上一个setlocal之后的操作呢?
作者: cjiabing    时间: 2011-4-24 12:14

百度搜索下:变量、环境变量、全局变量、局部变量、静态变量、变量名污染、堆栈……
内容虽然不是很多,可能与批处理也无关,不过一些观点还是值得参考的!~
作者: batman    时间: 2011-4-25 09:45

关于本次讨论的阶段性结论(个人版):
  
  1、cmd命令解释器在每一次运行时都向内存中重写一次系统变量信息(初始化系统变量),这就说明了为

什么我们无法在cmd中重设系统变量;

  2、cmd命令解释器将每产生的变量按照变量名大写字符的ansi序列机制临时写入内存中,形成一张变量表

并且在变量增加时会重写变量表,而当对已存在的变量进行读写操作时,则是按照ansi序列机制在变量表中进

行搜索比对一直到找到完全匹配的变量,那么当变量表中存在大量变量时,我们读写表前部的变量显然要比表

后部的变量要快,而出于效率上的考虑,我们在编写代码时应该尽量避免出现对大量的变量,同时尽量重复使

用同一变量名,万一要出现大量的变量,应使用setlocal-endlocal开关及时清空这些变量;

  3、对于setlocal-endlocal重写并复原变量的机制暂时没有搞清楚,因为在内存没有找到变量的第二个临时

储存位置。
作者: hanyeguxing    时间: 2011-4-25 10:01

本帖最后由 hanyeguxing 于 2011-4-25 11:34 编辑

对于:setlocal-endlocal
每运行一次setlocal将当前位置(本地环境变量位置,也就是命令解释器PSP中2C、2D字节所标记的环境段地址)的环境变量复制到临时位置,并将新变量继续写到当前位置,变量就是这么继承的...
运行endlocal运行时就清除当前位置所有变量,并将对应临时位置的变量复制回来。
这样,无论set还是其他命令(setlocal-endlocal除外),读写的都是本地环境变量位置里的变量
.............................................以上个人观点,哪天有人发现错了,记得不要拍砖。。。
测试:
  1. @echo off
  2. for /l %%a in (0,1,4) do set #%%a=%%a
  3. setlocal enableDelayedExpansion
  4. for /l %%a in (5,1,9) do set #%%a=%%a
  5. set #
  6. pause
  7. endlocal
复制代码
[attach]3768[/attach]
[attach]3769[/attach]
注意,内存分析时,批处理停在 pause 这条命令上,即没有执行 endlocal 呢......
作者: batman    时间: 2011-4-25 10:03

105# hanyeguxing
内存中的地址呢?怎么找不到?
作者: hanyeguxing    时间: 2011-4-25 10:06

本帖最后由 hanyeguxing 于 2011-4-25 11:46 编辑

debug时,只能很容易看到当前环境变量,因为临时的那些位置并不记录在PSP中(所有命令只读写当前环境变量)。。。我们可以换个内存数据工具软件来看嘛
作者: shuaige100    时间: 2011-5-1 21:10

各位都是高手,我都看不懂,你们也写个高效率的系统吧,让我们都开心开心
作者: techon    时间: 2011-5-9 13:41

77# zm900612


从当年MS-DOS开始,这里就标记环境变量的段地址值,到windows下都没变过。。。
hanyeguxing 发表于 2011-4-23 09:32


看来还是从DOS过来的厉害啊

不是庐山真面目,只缘身在此山中




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