Почему цикл GOTO намного медленнее цикла FOR и зависит дополнительно от источника питания? - PullRequest
0 голосов
/ 18 ноября 2018

Я написал небольшой пакетный файл для преобразования файла, содержащего восемь 16-битных шестнадцатеричных значений на строку, в файл CSV с восемью значениями в десятичном виде.

Файл входных данных представлял собой записанные выходные данные значений АЦП встроенного устройства, отправленные в пакетах из восьми шестнадцатеричных значений в ASCII с возвратом каретки плюс перевод строки через RS-232 на ПК и просто записанные на ПК в файл. Одна строка во входном файле данных была что-то вроде:

000A002D0044008B0125018C01F40237

CSV-файл для этой строки:

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

Пакетный файл работал, но потребовалось несколько минут, чтобы завершить преобразование, которое шокировало меня Я ожидал, что для выполнения этой задачи командный процессор Windows займет несколько секунд, а консольное приложение, написанное на C или C ++, может за несколько миллисекунд. Но время выполнения файла для данных размером менее 512 КиБ в несколько минут определенно не ожидалось.

Итак, я рассмотрел эту проблему далее, создав пакетный файл с использованием четырех различных методов для создания файла CSV с десятичными значениями из файла данных с шестнадцатеричными значениями.

Ниже приведен полный пакетный файл для тестирования четырех методов, а также результаты моих испытаний.

Я понимаю, что первые два метода, использующие подпрограмму, намного медленнее, чем два последних метода, выполняющих преобразование в одном цикле с выводом каждой строки CSV в файл на каждой итерации цикла соответственно один раз в конце FOR цикл из-за вызова подпрограммы вызывает несколько дополнительных шагов, выполняемых 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 на ПК, работающем от батареи, длится на 5-10 секунд дольше, чем на подключенном блоке питания.

Вопрос:

В чем причина гораздо более медленного выполнения цикла GOTO , используемого в методе 1, по сравнению с циклом FOR , используемого в методе 2, и почему метод 1 зависит от вида Блок питания ПК?


Вот весь пакетный файл, используемый для сравнения различных методов:

@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-файлы интересовались этими файлами.

Этот пакетный файл был выполнен мной четыре раза на двух ноутбуках.

Ниже приведены результаты первого запуска на ноутбуке с процессором Intel Core Duo P8400 с тактовой частотой 2,26 ГГц и 2 ГБ ОЗУ с жестким диском с частотой вращения 7200 об / мин под управлением 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 в 5,67 раза быстрее, чем метод 1. Методы 3 и 4 даже быстрее, чем метод 2, но я ожидаю этого. Большинство 2 и 3 секунд, необходимых для методов 3 и 4, относятся к команде WMIC для получения локальной даты и времени в формате, не зависящем от региона.

Вот результаты второго запуска на том же компьютере, что и первый запуск, с разницей при запуске компьютера на полностью заряженной батарее:

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 секунд, и поэтому это решение теперь примерно в 6,10 раз медленнее, чем у метода 2. Я понятия не имею, почему время обработки метода 1 зависит от типа источника питания.

ВотРезультаты первого запуска на ноутбуке с Intel Core Duo T9600 с тактовой частотой 2,80 ГГц и 4 ГБ ОЗУ с SSD под управлением Windows 7 x64 с подключенным источником питания:

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 в 5,86 раза быстрее, чем метод 1, только благодаря использованию FOR вместо цикла GOTO .

Для полноты результатов четвертого прогона на том же компьютере, что и третий прогон, с разницей при запуске ПК на полностью заряженном аккумуляторе:

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 голосов
/ 19 ноября 2018

Как уже говорилось, GOTO и CALL ищет следующее совпадение метки от текущей позиции файла до конца файла, затем он ищет от начала файла до текущей позиции файла.
Это поведение имеет некоторые полезные эффекты, так как вам не нужно беспокоиться о разных именах меток в функциях.

: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, поэтому дальнейших операций чтения с диска не требуется, и токенизация уже выполнена.

Похожие темы
Правила для имен меток против GOTO и CALL
Позвони мне, или лучше избегай звонить

0 голосов
/ 18 ноября 2018

Спасибо за ответы. Похоже, ты в порядке.

Причина в поиске метки, на которую ссылается 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

Потребовалось 34 секунд, чтобы выполнить задачу, которая была выполнена с большим тестовым пакетным файлом за 51 секунд на ноутбуке с Windows XP.

Затем я создал копию этого пакетного файла и вставил между goto :EOF и :ConvertLine блок с 250 строками с одинаковой строкой:

rem comment line of no interest

Этот пакетный файл с 9193 байтами требуется 64 секунд для выполнения точно такой же задачи.

Таким образом, поиск метки, которая составляет всего четыре строки вверх, определенно является причиной гораздо более продолжительного времени метода 1 по сравнению со способом 2. А метод 2 медленнее, чем метод 3, и 4 в основном , потому что по той же причине.

Но я до сих пор не выяснил, почему второму пакетному файлу с 9193 байтами требуется 72 секунд вместо 64 секунд на ноутбуке, работающем от батареи вместо подключенного блока питания. Пакетный файл и файл данных загружаются в кеше. Нет выходных данных при запуске командного файла. И я настроил в параметрах питания, чтобы использовать максимальную производительность также при работе от батареи. Сканирование на наличие метки в пакетном файле, очевидно, медленнее при работе от батареи, чем при подключенном блоке питания, хотя жесткий диск в действительности не доступен во время выполнения пакетного файла, только ядро ​​ЦП, кэш ЦП и ОЗУ.

Я также пытался использовать этот пакетный код, используя переменную среды 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

Этот пакетный файл закончился через 30 секунд при запуске на источнике питания, подключенном к Windows XP x86 с жестким диском ST980411ASG со скоростью 7200 об / мин (магнитный жесткий диск). Тот же пакетный файл, запущенный на батарее, занял 37 секунд для завершения на том же ПК.

Потребовалось 72 секунды на ПК с Windows 7 x64 с Samsung SSD 850 EVO (твердотельный диск) с подключенным источником питания и 77 секунды при работе от батареи. Я только что отключил электропитание между тестами, больше ничего не изменилось. Не было никакого подключения к какой-либо сети, WLAN отключен для каждого аппаратного коммутатора, Bluetooth отключен в BIOS, антивирусное приложение отключено во время выполнения (за исключением Защитника Windows в Windows 7x 64).

Я запускаю этот пакетный файл снова и снова на ПК с Windows 7 x64 и, наконец, во время выполнения довольно часто включается вентилятор, время выполнения становится постоянным с 72 секундами, независимо от того, включен источник питания или нет.

Для сравнения я выполнил также этот командный файл:

@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 потребовалось 1 или 2 секунды, чтобы завершить работу, при этом при подключенном блоке питания время окончания чаще всего составляло 1 секунду, а при работе от батареи время окончания - чаще всего 2 секунды. Мне нужно также учитывать миллисекунды, чтобы быть более точным в этом быстром финишном решении. Время выполнения составляло от 4 до 5 секунд на ПК под управлением Windows 7 x64 с той же тенденцией к сокращению времени на блоке питания, как на другом ноутбуке с Windows XP.

Светодиоды активности жесткого диска на обоих компьютерах не мигают по-разному по сравнению с тем, что пакетный файл не запущен. Я не слышу никакого другого звука с жесткого диска в Windows XP при запуске командного файла, который занимает около 30 секунд и еще больше при работе от батареи.

Но при использовании Process Monitor на обоих ПК я вижу, что сам пакетный файл постоянно открывается, читается, закрывается при запуске пакетного файла с использованием цикла GOTO , пока есть практически отсутствует доступ к пакетным файлам при использовании наиболее оптимизированной версии с циклом FOR .

И cmd.exe действительно считывает командный файл построчно снова и снова с помощью метода GOTO , как это видно и с Process Monitor на большом количестве ReadFile обращений с увеличением Offset с смещения идентичны смещениям начала каждой строки в пакетном файле. Время выполнения резко увеличивается при обращении к файловой системе записи Process Monitor из-за того, что отображается более двух миллионов записанных событий и более 500 000 событий.

Кроме того, в Process Monitor можно увидеть использование оптимизированного цикла FOR , в котором cmd.exe читает строки до конца цикла FOR , а затем считывает один раз целиком HexValues.dat с одним доступом ReadFile занимает 5 секунд (в Windows 7 x64), чтобы завершить преобразование из шестнадцатеричного в десятичное без какого-либо доступа к файловой системе, и затем читает остальные строки пакетного файла, чтобы завершить его выполнение. Process Monitor записывает только около 50 000 событий с отображением менее 100 событий.

Полагаю, что включение технологии Intel SpeedStep в BIOS является причиной увеличения времени выполнения пакетного файла с помощью команды GOTO с меткой над текущей командной строкой при работе от батареи. Это также объяснило бы, с моей точки зрения, тот эффект, что повторное выполнение второго опубликованного пакета в этом ответе снова и снова в Windows 7 x64 приводит к окончательному времени выполнения, независимо от того, включен ли источник питания, потому что Intel SpeedStep, наконец, повышает производительность процессора до максимума даже при работе от батареи, потому что одно ядро ​​постоянно работает на 100%.

Вывод:

Решение GOTO метода 1 намного медленнее, чем все другие методы, поскольку cmd.exe следит за достижением goto NextValue в соответствии с информацией, предоставленной Aacini и другими проанализировано и проверено с помощью 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 на материнской плате, в которой файл загружен из-за того, что весь пакетный файл не загружен во внутренний кэш ЦП.

Таким образом, с использованием цикла FOR в подпрограмме, как это делается в методе 2, количество действий доступа к пакетному файлу уже значительно сокращено, поскольку ни один из 21 шага не содержит гораздо больше операций доступа к файлу для чтенияпакетный файл построчно и не перечисленный явно все за один шаг необходимо выполнить при обработке шестнадцатеричных значений в текущей строке, считанной из HexValues.dat.

И все строки в HexValues.dat могут быть обработаны cmd.exe без какого-либо доступа к пакетному файлу при выполнении полного преобразования в одном цикле FOR , как это делается методами 3 и 4. И, наконец, можно сохранить еще несколько обращений к файловой системе, передав все строки CSV в STDOUT (буфер в памяти) и запись их всего один раз в файл CSV, как это сделано методом 4, сокращая еще раз общее время, необходимое для этой задачи преобразования значений, по сравнению с методом 3.

следует избегать наличия командной строки с goto LABEL или call :LABEL с LABEL выше текущей строки илимного строк ниже в большом пакетном файле делают что-то в цикле с сотнями или тысячами итераций.Сложный и непростой для понимания цикл FOR для неопытных в пакетном кодировании файлов в таких случаях лучше, чем удобочитаемое решение, использующее GOTO или CALL .Или, другими словами, всегда желательно использовать просто цикл FOR для выполнения чего-то в цикле действительно часто.

0 голосов
/ 18 ноября 2018

Это из-за способа работы goto.В отличие от скомпилированного языка программирования, где goto преобразуется во время компиляции в фиксированный адрес, пакет должен искать в файле метки для каждого goto.

Он ищет файл вниз от строки goto и, если не находит метку, продолжает поиск с начала файла.

0 голосов
/ 18 ноября 2018

Согласно этого анализа интерпретатора, переменная FOR будет развернута на этапе 4, поэтому интерпретатор будет знать, сколько раз выполнить команду и какие значения сразу.Напротив, каждое GOTO интерпретируется на этапе 7 и требует повторного сканирования файла, каждый раз ища метку, которая учитывает воспринимаемую разницу во времени.

0 голосов
/ 18 ноября 2018

Если я правильно помню, команда GOTO LABEL в пакетном файле фактически сканирует оставшуюся часть текста в поисках метки, и если она не находит ее, она перезапускается сверху.

Этодовольно дорогая операция, и я думаю, что CALL тоже делает это.

Так что, если вы заботитесь о производительности, вам следует избегать этих конструкций.Или, что еще лучше, не выполняйте такую ​​работу в пакетных файлах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...