为什么GOTO循环比FOR循环慢得多,并且还要依靠电源?

时间:2018-11-18 16:19:56

标签: windows batch-file cmd

我写了一个小批处理文件,将每行包含八个16位十六进制值的文件转换为十进制八个值的CSV文件。

输入数据文件是捕获的嵌入式设备ADC值的输出,以ASCII形式的八个十六进制值的数据包以回车符加上通过RS-232到PC的换行符发送到PC,并简单地在PC上捕获到文件中。输入数据文件中的一行类似于:

000A002D0044008B0125018C01F40237

此行的CSV文件为:

10,45,68,139,293,396,500,567

该批处理文件有效,但是花了几分钟才完成转换,这令我震惊。我预计Windows命令处理器会花几秒钟来完成此任务,而用C或C ++编写的控制台应用程序可能会在几毫秒内完成。但是我绝对不希望对小于512 KiB的数据文件执行几分钟的时间。

因此,我通过使用四种不同的方法来创建批处理文件以从具有十六进制值的数据文件中创建具有十进制值的CSV文件来进一步解决这个问题。

下面是用于测试这四种方法的完整批处理文件以及我的测试结果。

我了解到,使用子例程的前两种方法要比后两种方法在一个循环中进行转换要慢得多,后者在 FOR 结束时在每次循环迭代中分别将每个CSV行输出到文件中>由于调用子例程而导致循环,这导致cmd.exe还要执行几个额外的步骤,因此执行子例程调用需要花费大量时间。

但是我真的不明白为什么使用 GOTO 循环的第一种方法比使用几乎相同的两个命令行的 FOR 循环慢大约六倍。

方法1的批处理文件代码:

@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "DelDataFile="
set "DataFile=%TEMP%\HexValues.dat"
if not exist "%DataFile%" set "DelDataFile=1" & echo 000A002D0044008B0125018C01F40237>"%DataFile%"

for /F "usebackq delims=" %%I in ("%DataFile%") do call :ConvertLine "%%I"

if defined DelDataFile del "%DataFile%"
endlocal
goto :EOF

:ConvertLine
set "DataLine=%~1"
set "AllValues="
set "StartColumn=0"
:NextValue
set /A Value=0x!DataLine:~%StartColumn%,4!
set "AllValues=%AllValues%,%Value%"
set /A StartColumn+=4
if not %StartColumn% == 32 goto NextValue
echo %AllValues:~1%
goto :EOF

方法2的批处理文件代码:

@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "DelDataFile="
set "DataFile=%TEMP%\HexValues.dat"
if not exist "%DataFile%" set "DelDataFile=1" & echo 000A002D0044008B0125018C01F40237>"%DataFile%"

for /F "usebackq delims=" %%I in ("%DataFile%") do call :LineConvert "%%I"

if defined DelDataFile del "%DataFile%"
endlocal
goto :EOF

:LineConvert
set "DataLine=%~1"
set "AllValues="
for /L %%J in (0,4,28) do (
    set /A Value=0x!DataLine:~%%J,4!
    set "AllValues=!AllValues!,!Value!"
)
echo !AllValues:~1!
goto :EOF

我还通过运行测试发现了一个原因,我自己发现方法1在使用电池供电的PC上比在插入电源的情况下花费5至10秒的时间。

问题:

与方法2使用的 FOR 循环相比,方法1使用的 GOTO 循环执行慢得多的原因是什么?为什么方法1依赖于什么样的PC电源?


以下是整个批处理文件,用于比较不同的方法:

@echo off
setlocal EnableExtensions EnableDelayedExpansion
cls
set "TestRuns=5"
set "DelDataFile="
set "DataFile=%TEMP%\HexValues.dat"
if exist "%DataFile%" goto InitMethod1

set "DelDataFile=1"
echo Creating data file which takes some seconds, please wait ...
setlocal
set "HexDigits=0123456789ABCDEF"
set "DataLine="
(for /L %%I in (0,1,32767) do (
    set /A "Digit1=(%%I >> 12) %% 16"
    set /A "Digit2=(%%I >> 8) %% 16"
    set /A "Digit3=(%%I >> 4) %% 16"
    set /A "Digit4=%%I %% 16"
    set "HexValue="
    for %%J in (!Digit1! !Digit2! !Digit3! !Digit4!) do set "HexValue=!HexValue!!HexDigits:~%%J,1!"
    set "DataLine=!DataLine!!HexValue!"
    set /A "ValuesPerLine=%%I %% 8"
    if !ValuesPerLine! == 7 (
        echo !DataLine!
        set "DataLine="
    )
))>"%DataFile%"
endlocal
echo/


:InitMethod1
call :MethodInit 1
:RunMethod1
set /A TestRun+=1
set "CSV_File=%TEMP%\Values%Method%_%TestRun%.csv"
del "%CSV_File%" 2>nul
call :GetTime StartTime

for /F "usebackq delims=" %%I in ("%DataFile%") do call :ConvertLine "%%I"

call :OutputTime
if %TestRun% LSS %TestRuns% goto RunMethod1
call :MethodResults
goto InitMethod2

:ConvertLine
set "DataLine=%~1"
set "AllValues="
set "StartColumn=0"
:NextValue
set /A Value=0x!DataLine:~%StartColumn%,4!
set "AllValues=%AllValues%,%Value%"
set /A StartColumn+=4
if not %StartColumn% == 32 goto NextValue
>>"%CSV_File%" echo %AllValues:~1%
goto :EOF


:InitMethod2
call :MethodInit 2
:RunMethod2
set /A TestRun+=1
set "CSV_File=%TEMP%\Values%Method%_%TestRun%.csv"
del "%CSV_File%" 2>nul
call :GetTime StartTime

for /F "usebackq delims=" %%I in ("%DataFile%") do call :LineConvert "%%I"

call :OutputTime
if %TestRun% LSS %TestRuns% goto RunMethod2
call :MethodResults
goto InitMethod3

:LineConvert
set "DataLine=%~1"
set "AllValues="
for /L %%J in (0,4,28) do (
    set /A Value=0x!DataLine:~%%J,4!
    set "AllValues=!AllValues!,!Value!"
)
echo !AllValues:~1!>>"%CSV_File%"
goto :EOF


:InitMethod3
call :MethodInit 3
:RunMethod3
set /A TestRun+=1
set "CSV_File=%TEMP%\Values%Method%_%TestRun%.csv"
del "%CSV_File%" 2>nul
call :GetTime StartTime

for /F "usebackq delims=" %%I in ("%DataFile%") do (
    set "DataLine=%%I"
    set "AllValues="
    for /L %%J in (0,4,28) do (
        set /A Value=0x!DataLine:~%%J,4!
        set "AllValues=!AllValues!,!Value!"
    )
    echo !AllValues:~1!>>"%CSV_File%"
)

call :OutputTime
if %TestRun% LSS %TestRuns% goto RunMethod3
call :MethodResults
goto InitMethod4


:InitMethod4
call :MethodInit 4
:RunMethod4
set /A TestRun+=1
set "CSV_File=%TEMP%\Values%Method%_%TestRun%.csv"
del "%CSV_File%" 2>nul
call :GetTime StartTime

(for /F "usebackq delims=" %%I in ("%DataFile%") do (
    set "DataLine=%%I"
    set "AllValues="
    for /L %%J in (0,4,28) do (
        set /A Value=0x!DataLine:~%%J,4!
        set "AllValues=!AllValues!,!Value!"
    )
    echo !AllValues:~1!
))>>"%CSV_File%"

call :OutputTime
if %TestRun% LSS %TestRuns% goto RunMethod4
call :MethodResults
goto EndBatch


:GetTime
for /F "tokens=2 delims==." %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do set "%1=%%I"
goto :EOF

:MethodInit
set "Method=%1"
echo Test runs with method %Method%
echo -----------------------
echo/
set "TestRun=0"
set "TotalTime=0"
goto :EOF

:MethodResults
set /A AverageTime=TotalTime / TestRun
echo Method %Method% total time: %TotalTime% seconds
echo Method %Method% average time: %AverageTime% seconds
echo/
goto :EOF

:OutputTime
call :GetTime EndTime
set /A StartTime=(1%StartTime:~8,2% - 100) * 3600 + (1%StartTime:~10,2% - 100) * 60 + 1%StartTime:~12,2% - 100
set /A EndTime=(1%EndTime:~8,2% - 100) * 3600 + (1%EndTime:~10,2% - 100) * 60 + 1%EndTime:~12,2% - 100
set /A DiffTime=EndTime - StartTime
set /A TotalTime+=DiffTime
echo Method %Method% run %TestRun% time: %DiffTime% seconds
goto :EOF


:EndBatch
if defined DelDataFile del "%DataFile%"
del /Q "%TEMP%\Values?_*.csv"
endlocal

它首先为临时文件创建文件夹中具有递增十六进制值的数据文件,该文件已经花费了几秒钟。请批注此批处理文件的最后一条命令行以保留该文件,以防多次运行此批处理文件或对该文件感兴趣。

然后,它运行四种方法的五倍,这四种方法是从数据文件中读取十六进制值并将十进制值写入CSV文件,然后将测试结果打印到控制台,分别处理 STDOUT

最后,它将删除也在文件夹中为所有具有相同内容的临时文件创建的所有CSV文件。请在最后一个注释中添加一个命令行,以使这些CSV文件对这些文件感兴趣。

这个批处理文件由我在两个笔记本上执行了四次。

以下是首次在笔记本电脑上运行的结果,该笔记本电脑具有2.26 GHz的Intel Core Duo P8400和2个具有7200 rpm HDD的硬盘,运行Windows XP x86并插入了电源:

Test runs with method 1
-----------------------

Method 1 run 1 time: 51 seconds
Method 1 run 2 time: 51 seconds
Method 1 run 3 time: 51 seconds
Method 1 run 4 time: 52 seconds
Method 1 run 5 time: 51 seconds
Method 1 total time: 256 seconds
Method 1 average time: 51 seconds

Test runs with method 2
-----------------------

Method 2 run 1 time: 9 seconds
Method 2 run 2 time: 9 seconds
Method 2 run 3 time: 9 seconds
Method 2 run 4 time: 8 seconds
Method 2 run 5 time: 9 seconds
Method 2 total time: 44 seconds
Method 2 average time: 9 seconds

Test runs with method 3
-----------------------

Method 3 run 1 time: 3 seconds
Method 3 run 2 time: 3 seconds
Method 3 run 3 time: 4 seconds
Method 3 run 4 time: 3 seconds
Method 3 run 5 time: 3 seconds
Method 3 total time: 16 seconds
Method 3 average time: 3 seconds

Test runs with method 4
-----------------------

Method 4 run 1 time: 3 seconds
Method 4 run 2 time: 2 seconds
Method 4 run 3 time: 2 seconds
Method 4 run 4 time: 2 seconds
Method 4 run 5 time: 2 seconds
Method 4 total time: 11 seconds
Method 4 average time: 2 seconds

方法2比方法1快5.67倍。方法3和4甚至比方法2快,但这是我所期望的。方法3和4所需的2秒和3秒大部分来自 WMIC 命令,以采用与区域无关的格式获取本地日期和时间。

以下是第二次与第一次运行在同一台计算机上的结果,不同之处在于使用充满电的电池运行PC:

Test runs with method 1
-----------------------

Method 1 run 1 time: 63 seconds
Method 1 run 2 time: 61 seconds
Method 1 run 3 time: 61 seconds
Method 1 run 4 time: 61 seconds
Method 1 run 5 time: 61 seconds
Method 1 total time: 307 seconds
Method 1 average time: 61 seconds

Test runs with method 2
-----------------------

Method 2 run 1 time: 11 seconds
Method 2 run 2 time: 10 seconds
Method 2 run 3 time: 10 seconds
Method 2 run 4 time: 10 seconds
Method 2 run 5 time: 10 seconds
Method 2 total time: 51 seconds
Method 2 average time: 10 seconds

Test runs with method 3
-----------------------

Method 3 run 1 time: 3 seconds
Method 3 run 2 time: 4 seconds
Method 3 run 3 time: 3 seconds
Method 3 run 4 time: 4 seconds
Method 3 run 5 time: 3 seconds
Method 3 total time: 17 seconds
Method 3 average time: 3 seconds

Test runs with method 4
-----------------------

Method 4 run 1 time: 2 seconds
Method 4 run 2 time: 2 seconds
Method 4 run 3 time: 2 seconds
Method 4 run 4 time: 2 seconds
Method 4 run 5 time: 2 seconds
Method 4 total time: 10 seconds
Method 4 average time: 2 seconds

可以看出,对于方法2到方法4,处理时间只会增加一点点。但是方法1的处理时间增加了10秒,因此该解决方案现在比方法2慢6.10倍。我不知道为什么方法1的处理时间取决于电源的类型。

这是首次在笔记本电脑上运行的结果,该笔记本电脑具有2.80 GHz的Intel Core Duo T9600和4 GiB RAM,以及运行Windows 7 x64并插入电源的SSD,

Test runs with method 1
-----------------------

Method 1 run 1 time: 91 seconds
Method 1 run 2 time: 88 seconds
Method 1 run 3 time: 77 seconds
Method 1 run 4 time: 77 seconds
Method 1 run 5 time: 78 seconds
Method 1 total time: 411 seconds
Method 1 average time: 82 seconds

Test runs with method 2
-----------------------

Method 2 run 1 time: 11 seconds
Method 2 run 2 time: 16 seconds
Method 2 run 3 time: 16 seconds
Method 2 run 4 time: 14 seconds
Method 2 run 5 time: 16 seconds
Method 2 total time: 73 seconds
Method 2 average time: 14 seconds

Test runs with method 3
-----------------------

Method 3 run 1 time: 6 seconds
Method 3 run 2 time: 4 seconds
Method 3 run 3 time: 4 seconds
Method 3 run 4 time: 4 seconds
Method 3 run 5 time: 6 seconds
Method 3 total time: 24 seconds
Method 3 average time: 4 seconds

Test runs with method 4
-----------------------

Method 4 run 1 time: 4 seconds
Method 4 run 2 time: 3 seconds
Method 4 run 3 time: 5 seconds
Method 4 run 4 time: 4 seconds
Method 4 run 5 time: 4 seconds
Method 4 total time: 20 seconds
Method 4 average time: 4 seconds

有趣的是,在Windows 7 x64上使用功能更强大的硬件执行批处理文件比在Windows XP x86上花费更多时间。但是对我而言,更有趣的是,方法2比方法1快5.86倍,这仅仅是因为使用了 FOR 而不是 GOTO 循环。

为完整起见,第四次运行与第三次运行在同一台计算机上的结果,与在充满电的电池上运行PC的区别在于:

Test runs with method 1
-----------------------

Method 1 run 1 time: 97 seconds
Method 1 run 2 time: 91 seconds
Method 1 run 3 time: 90 seconds
Method 1 run 4 time: 81 seconds
Method 1 run 5 time: 77 seconds
Method 1 total time: 436 seconds
Method 1 average time: 87 seconds

Test runs with method 2
-----------------------

Method 2 run 1 time: 12 seconds
Method 2 run 2 time: 16 seconds
Method 2 run 3 time: 17 seconds
Method 2 run 4 time: 16 seconds
Method 2 run 5 time: 13 seconds
Method 2 total time: 74 seconds
Method 2 average time: 14 seconds

Test runs with method 3
-----------------------

Method 3 run 1 time: 6 seconds
Method 3 run 2 time: 6 seconds
Method 3 run 3 time: 5 seconds
Method 3 run 4 time: 5 seconds
Method 3 run 5 time: 5 seconds
Method 3 total time: 27 seconds
Method 3 average time: 5 seconds

Test runs with method 4
-----------------------

Method 4 run 1 time: 4 seconds
Method 4 run 2 time: 4 seconds
Method 4 run 3 time: 5 seconds
Method 4 run 4 time: 4 seconds
Method 4 run 5 time: 4 seconds
Method 4 total time: 21 seconds
Method 4 average time: 4 seconds

与插入电源的第三次运行相比,方法3到4的执行时间再次没有太大差异。但是方法1的执行时间增加了大约5秒,因此方法1是6.21倍比方法2慢。

我真的会对为什么方法1比方法2慢得多并且还取决于电源的种类感兴趣。

由于Windows文件缓存,在所有测试运行中,硬盘活动指示灯仅很少闪烁。

5 个答案:

答案 0 :(得分:4)

如果我没记错的话,批处理文件中的GOTO LABEL命令实际上会扫描文本的其余部分以查找标签,如果找不到,则会在顶部重新启动。

这是一个非常昂贵的操作,而且我认为CALL也会这样做。

因此,如果您关心性能,则应该尝试避免使用这些构造。或者,甚至更好的是,不要在批处理文件中进行这种工作。

答案 1 :(得分:4)

根据解释器的this analysis,将在阶段4中扩展FOR变量,因此解释器将知道执行该命令多少次以及立即获得什么值。相比之下,每个GOTO都会在第7阶段进行解释,并且它每次都需要重新扫描文件以查找标签,这说明了感知到的时差。

答案 2 :(得分:3)

这是因为goto的工作方式。与已编译的编程语言不同,在编译过程中,goto会转换为固定地址,而批处理则必须在文件中搜索每个goto的标签。

它从goto行向下搜索文件,如果找不到标签,则从文件开头继续搜索。

答案 3 :(得分:1)

如上所述,GOTOCALL搜索从当前文件位置到文件末尾的下一个与标签匹配的下一个,然后从文件开始搜索直到当前文件位置。 br /> 此行为具有一些有用的效果,因为您不必担心函数中的不同标签名称。

:myFunc1
<some code>
goto :someLabel  -- this goto's to the next :someLabel  

:someLabel

:myFunc2
<some code>
goto :someLabel  -- this goto's to the next :someLabel  

:someLabel  

但是,在构建循环时,它具有很大的速度优势,因为完整的FOR括号块将只读取一次并解析到阶段2。
解析的块位于cmd缓存中,因此不再需要进行任何其他磁盘读取,并且标记化已经完成。

相关主题
Rules for label names vs GOTO and CALL
CALL me, or better avoid call

答案 4 :(得分:0)

感谢您的回答。看来您还好。

原因是在批处理文件中首先搜索 GOTO 所引用的标签,然后从顶部开始搜索,直到找不到文件底部为止。

我验证了首先使用带有以下行且文件大小为941字节的批处理文件:

@echo off
setlocal EnableExtensions EnableDelayedExpansion
for /F "tokens=2 delims==." %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do set "StartTime=%%I"
for /F "usebackq delims=" %%I in ("%TEMP%\HexValues.dat") do call :ConvertLine "%%I"
for /F "tokens=2 delims==." %%I in ('%SystemRoot%\System32\wbem\wmic.exe OS GET LocalDateTime /VALUE') do set "EndTime=%%I"
set /A StartTime=(1%StartTime:~8,2% - 100) * 3600 + (1%StartTime:~10,2% - 100) * 60 + 1%StartTime:~12,2% - 100
set /A EndTime=(1%EndTime:~8,2% - 100) * 3600 + (1%EndTime:~10,2% - 100) * 60 + 1%EndTime:~12,2% - 100
set /A DiffTime=EndTime - StartTime
echo Time: %DiffTime% seconds
endlocal
goto :EOF

:ConvertLine
set "DataLine=%~1"
set "AllValues="
set "StartColumn=0"
:NextValue
set /A Value=0x!DataLine:~%StartColumn%,4!
set "AllValues=%AllValues%,%Value%"
set /A StartColumn+=4
if not %StartColumn% == 32 goto NextValue
goto :EOF

在装有Windows XP的笔记本电脑上,34秒内完成了51秒内完成的任务,该任务由较大的测试批处理文件完成。

然后我创建了该批处理文件的副本,并插入了goto :EOF:ConvertLine之间的一个包含250行且都带有相同字符串的块:

rem comment line of no interest

这个具有9193字节的批处理文件需要64秒才能完成完全相同的任务。

因此,搜索仅向上四行的标签绝对是方法1与方法2相比时间更长的原因。方法2比方法3和方法4慢主要因为同样的原因。

但是我仍然没有找到为什么第二个9193字节的批处理文件需要72秒而不是64秒,而笔记本电脑需要电池供电,而不是插入电源。数据文件被加载到缓存中。运行批处理文件没有输出。而且我已经在电源选项中配置了电池供电,以也能发挥最佳性能。使用电池运行时,扫描批处理文件中的标签显然比插入电源慢,尽管在批处理文件执行过程中并没有真正访问硬盘,而只是访问CPU内核,CPU缓存和RAM。

我也尝试了使用与区域相关的TIME环境变量而不是使用 WMIC 命令来获取与区域无关的日期/时间的批处理代码。 %TIME%在我的电脑上扩展为德语时间格式HH::MM:SS.ms

@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "StartTime=%TIME%"
for /F "usebackq delims=" %%I in ("%TEMP%\HexValues.dat") do call :ConvertLine "%%I"
set "EndTime=%TIME%"
set /A StartTime=(1%StartTime:~0,2% - 100) * 3600 + (1%StartTime:~3,2% - 100) * 60 + 1%StartTime:~6,2% - 100
set /A EndTime=(1%EndTime:~0,2% - 100) * 3600 + (1%EndTime:~3,2% - 100) * 60 + 1%EndTime:~6,2% - 100
set /A DiffTime=EndTime - StartTime
echo Time: %DiffTime%
endlocal
goto :EOF

:ConvertLine
set "DataLine=%~1"
set "AllValues="
set "StartColumn=0"
:NextValue
set /A Value=0x!DataLine:~%StartColumn%,4!
set "AllValues=%AllValues%,%Value%"
set /A StartColumn+=4
if not %StartColumn% == 32 goto NextValue
goto :EOF

此批处理文件在{7}上以在运行Windows XP x86的电源(具有7200 rpm的HDD ST980411ASG)上运行的电源(磁性硬盘)上运行,30秒后完成。接下来用电池运行的同一批处理文件需要37秒在同一台PC上完成。

在装有带电源的Samsung SSD 850 EVO(固态磁盘)的Windows 7 x64的PC上花费了72秒,而用电池运行则花费了77秒。我只是在测试运行之间拔掉了电源,其他没有改变。没有与任何网络的连接,每个硬件开关都关闭了WLAN,在BIOS中禁用了蓝牙,在执行过程中禁用了防病毒应用程序(Windows 7x 64上的Windows Defender除外)。

我在装有Windows 7 x64的PC上一次又一次运行此批处理文件,最后在执行过程中频繁转动风扇,执行时间以72秒为常数,而与是否插入电源无关。 / p>

为了进行比较,我还执行了此批处理文件:

@echo off
setlocal EnableExtensions EnableDelayedExpansion
set "StartTime=%TIME%"
for /F "usebackq delims=" %%I in ("%TEMP%\HexValues.dat") do (
    set "DataLine=%%I"
    set "AllValues="
    for /L %%J in (0,4,28) do (
        set /A Value=0x!DataLine:~%%J,4!
        set "AllValues=!AllValues!,!Value!"
    )
)
set "EndTime=%TIME%"
set /A StartTime=(1%StartTime:~0,2% - 100) * 3600 + (1%StartTime:~3,2% - 100) * 60 + 1%StartTime:~6,2% - 100
set /A EndTime=(1%EndTime:~0,2% - 100) * 3600 + (1%EndTime:~3,2% - 100) * 60 + 1%EndTime:~6,2% - 100
set /A DiffTime=EndTime - StartTime
echo Time: %DiffTime%
endlocal

在运行Windows XP的PC上需要1或2秒才能完成,而在接通电源的情况下,完成时间通常是1秒,而在使用电池供电时,完成时间通常是2秒。我还需要考虑毫秒,以便在此快速整理解决方案上更加精确。在PC Windows 7 x64上,执行时间在4到5秒之间,这与在其他装有Windows XP的笔记本电脑上一样,可以缩短电源插入时间。

与不运行批处理文件相比,两台计算机上的硬盘活动发光二极管没有闪烁。运行批处理文件大约需要30秒钟才能完成,而依靠电池运行则需要更多时间,我听不到Windows XP上的HDD发出任何不同的声音。

但是我可以看到在两台PC上使用Process Monitor时,在使用 GOTO 循环运行批处理文件时,批处理文件本身都是永久打开,读取和关闭的,而几乎没有使用具有 FOR 循环的最优化版本来批量访问文件。

cmd.exe实际上使用 GOTO 方法一次又一次地读取批处理文件,因为使用Process Monitor也可以通过大量ReadFile访问来读取该批处理文件。增加Offset的偏移量与批处理文件中每行开头的偏移量相同。由于记录了超过200万个事件并显示了500,000个事件,因此在Process Monitor记录文件系统访问时,执行时间急剧增加。

此外,通过Process Monitor可以看到,仅使用优化的 FOR 循环,cmd.exe读取直到 FOR 循环结束的行,然后只需一次访问HexValues.dat即可读取整个ReadFile一次,在没有任何文件系统访问的情况下,需要5秒(在Windows 7 x64上)完成从十六进制到十进制的转换,然后读取批处理文件的其余行完成执行。 Process Monitor仅记录大约50,000个事件,而显示的事件少于100个。

我认为在BIOS中启用Intel SpeedStep技术是使用 GOTO 命令(带有当前电池上方的当前命令行上方的标签)执行批处理文件的时间增加的原因。在我看来,这也可以解释这样的效果:在Windows 7 x64上一次又一次在此答案中运行第二个发布的批处理最终会导致恒定的执行时间,而与是否插入电源无关,这是因为Intel SpeedStep最终提高了性能由于一个内核永久以100%的速度运行,因此即使使用电池运行,CPU的最大利用率也可以达到最大。

结论:

方法1的 GOTO 解决方案比所有其他方法都要慢得多,因为Aacini给出的信息是cmd.exe在到达goto NextValue时会遵循其余的则通过Process Monitor进行了分析和验证:

  1. 打开批处理文件以读取和查询有关文件的标准信息,以检查自上次访问以来批处理文件是否已更改。在下面的其他步骤中,不会查询标准信息。
  2. 从批处理文件中依次读取一行,并处理每一行以找到:NextValue行,直到文件末尾都没有成功。
  3. 到达文件末尾时快退到文件顶部。
  4. 现在从顶部开始从批处理文件中依次读取一行,并处理每一行以找到:NextValue行。
  5. 在找到第:NextValue行并知道下一个要处理的命令行的偏移量后,关闭批处理文件。
  6. 再次打开批处理文件。
  7. 读取set /A Value=0x!DataLine:~%StartColumn%,4!行。
  8. 再次关闭该批处理文件。
  9. 处理读取的命令行。
  10. 再次打开批处理文件。
  11. 从批处理文件中读取下一行set "AllValues=%AllValues%,%Value%"
  12. 关闭批处理文件。
  13. 处理读取的命令行。
  14. 再次打开批处理文件。
  15. 从批处理文件中读取下一行set /A StartColumn+=4
  16. 关闭批处理文件。
  17. 处理读取的命令行。
  18. 再次打开批处理文件。
  19. 从批处理文件中读取下一行if not %StartColumn% == 32 goto NextValue
  20. 关闭批处理文件。
  21. 处理读取的命令行。
  22. 如果条件为真,即StartColumn不是32,则继续执行第一步。

所有这些批处理文件打开/读取/关闭操作即使不访问存储介质也要花费几毫秒,但是访问主板上的DRAM的可能性最可能由于未将所有批处理文件内容加载到CPU的内部缓存中,因此文件被加载。

因此,如方法2所述,通过在子例程中使用 FOR 循环,批处理文件访问操作的数量已经大大减少,因为21步中没有一个具有读取更多文件访问操作的步骤在处理从HexValues.dat读取的当前行中的十六进制值时,需要逐行处理批处理文件,并且未在所有步骤中明确列出所有批处理文件。

HexValues.dat中的所有行都可以由cmd.exe处理,而在通过方法3完成的一个 FOR 循环中进行整个转换时,无需访问批处理文件。和4。最后,通过将所有CSV行输出到 STDOUT (内存中的缓冲区)并将它们仅写入一次到CSV文件中(如方法4所述),可以保存更多的文件系统访问权限。与方法3相比,此值转换任务所需的总时间。

应避免将命令行中的goto LABELcall :LABEL放在LABEL的当前行之上,或者将其放在较大批处理文件的下面的许多行中,以成百上千的循环进行或数千次迭代。在这种情况下,对于批处理文件编码中的非专家来说,一个复杂且分别不易理解的 FOR 循环要比使用 GOTO CALL 的可读性更好的解决方案更好。强>。或者换句话说,总是建议只使用 FOR 循环来真正经常地执行循环操作。