Faster batch macros
Posted: 05 Jan 2024 14:29
Hi,
while I try to build a small batch list selection component for a customer, I stumbled about the horrible, poor performance of batch functions on network drives.
My code used the standard :strlen function 180 times and it takes some seconds.
I decided to use a strlen macro, it's faster, but still not satisfying, so I checked the performance killers.
Mac1
The main performance issue are the two setlocal/endlocals per macro execution, I measured one pair of setlocal/endlocal takes ~1ms.
Mac2 Removing one pair was easy, as the inner pair is only for the argv variable.
Mac3 Removing the last setlocal EnableDelayedExpansion makes the macro nearly three times faster than before, but this has two drawbacks.
1. There are now three variables polluting the environment (can be optimized to two variables)
2. The macro only works if EnableDelayedExpansion was already active
It's okay for me to pollute the environment by some variables, but the macro should work always, independent of the delayed expansion mode.
But it should enable delayed expansion only when necessary.
Mac4
Here is the trick to get the current state and store it in a for meta variable with:
It stores in %%E an D, if the current mode is DisableDelayedExpansion(DDE), else two double quotes.
This can be uses in an IF statement or just another FOR
If it's in DDE, then %%~E expands to D and the DO part will be executed, else %%~E expands to nothing and the DO part will be skipped
Mac5
This technique can also be useful, when it's necessary for returning non trivial values to a known delayed expansion mode.
while I try to build a small batch list selection component for a customer, I stumbled about the horrible, poor performance of batch functions on network drives.
My code used the standard :strlen function 180 times and it takes some seconds.
I decided to use a strlen macro, it's faster, but still not satisfying, so I checked the performance killers.
Mac1
Code: Select all
(set ^"$\n=^^^
%= This creates an escaped Line Feed - DO NOT ALTER =%
)
set $strLen=for %%# in (1 2) do if %%#==2 ( %$\n%
for /f "tokens=1,2 delims=, " %%1 in ("!argv!") do ( %$\n%
%= *** remove the local variable "argv" =% %$\n%
endlocal %$\n%
%= *** copy content to temporary variable, the carets are for extended length, up to 8191chars =% %$\n%
(set^^ s=!%%~2!) %$\n%
if defined s ( %$\n%
set "len=1" %$\n%
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do ( %$\n%
if "!s:~%%P,1!" neq "" ( %$\n%
set /a "len+=%%P" %$\n%
set "s=!s:~%%P!" %$\n%
) %$\n%
) %$\n%
) ELSE set "len=0" %$\n%
for %%V in (!len!) do endlocal ^& set "%%~1=%%V" %$\n%
) %$\n%
) else setlocal EnableDelayedExpansion ^& setlocal ^& set argv=,
Mac2 Removing one pair was easy, as the inner pair is only for the argv variable.
Mac3 Removing the last setlocal EnableDelayedExpansion makes the macro nearly three times faster than before, but this has two drawbacks.
1. There are now three variables polluting the environment (can be optimized to two variables)
2. The macro only works if EnableDelayedExpansion was already active
Code: Select all
Macro | DDE | EDE
-------------------
Mac1 | 6370 | 6370
Mac2 | 4700 | 4700
Mac3 | fail | 2600
Mac4 | 4700 | 2600
Mac5 | 4800 | 2700
But it should enable delayed expansion only when necessary.
Mac4
Here is the trick to get the current state and store it in a for meta variable with:
Code: Select all
for /F "tokens=2" %%E in ("!! D """) DO
This can be uses in an IF statement or just another FOR
Code: Select all
( for %%X in (%%~E) DO setlocal EnableDelayedExpansion)
Mac5
Code: Select all
(set ^"$\n=^^^
%= This creates an escaped Line Feed - DO NOT ALTER =%
)
set $strLen=for /F "tokens=2" %%E in ("!! D """) DO for %%# in (1 2) do if %%#==2 ( %$\n%
for /f "tokens=1,2 delims=, " %%1 in ("!_!") do ( %$\n%
%= *** copy content to temporary variable, the carets are for extended length, up to 8191chars =% %$\n%
(set^^ _=!%%~2!) %$\n%
if defined _ ( %$\n%
set "_len=1" %$\n%
for %%P in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do ( %$\n%
if "!_:~%%P,1!" neq "" ( %$\n%
set /a "_len+=%%P" %$\n%
set "_=!_:~%%P!" %$\n%
) %$\n%
) %$\n%
) ELSE set "_len=0" %$\n%
for %%V in (!_len!) do ( for %%E in (%%~E) DO endlocal ) ^& set "%%~1=%%V" %$\n%
) %$\n%
) else ( for %%E in (%%~E) DO setlocal EnableDelayedExpansion) ^& set _=,