strLen boosted

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
aGerman
Expert
Posts: 4743
Joined: 22 Jan 2010 18:01
Location: Germany

Re: strLen boosted

#76 Post by aGerman » 13 Aug 2025 12:20

This is so impressive :!:

BTW Was this known to work?

Code: Select all

if not defined %%1 (if %%? lss ' endlocal)^&(set /a %%2=0) else^
This would have been my expectation:

Code: Select all

if not defined %%1 ((if %%? lss ' endlocal)^&set /a %%2=0) else^

pieh-ejdsch
Posts: 257
Joined: 04 Mar 2014 11:14
Location: germany

Re: strLen boosted

#77 Post by pieh-ejdsch » 13 Aug 2025 15:12

I saw that in Francesco's code.
Then I tried it out.

IF (a TRUE condition) & some more code that should also be fulfilled if TRUE & then something else that should be done if TRUE & (then the last little thing for TRUE) ELSE now the code that comes up if FALSE & something else that comes up if FALSE

I didn't want to flip the logic around,
but with

Code: Select all

(if a==a call) && (if b==a call) && ... 
I could use the IF condition at the end without the entire bracket and continue with a simple &.
So the same bracket logic as with

Code: Select all

(if ... (..)&..&(..) else ..)
.
Since the IF DEFINED always slowed down the test code when it was removed,
and even when a bracket was placed in front of the if,
I tried it with this chaining (call)&&.
All attempts always took longer to run.
I also had a (call) after the last FOR, but the chaining became slower.
So the else had to be moved to the front to make it run better.

aGerman
Expert
Posts: 4743
Joined: 22 Jan 2010 18:01
Location: Germany

Re: strLen boosted

#78 Post by aGerman » 13 Aug 2025 16:19

I'm still trying to address other given feedback without performance impact.

Code: Select all

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:initStrLen
:: Computes the number of resulting UTF-16 code units in a string.
:: %strLen% str [len]
::   str - [ByRef In] Name of the variable containing the string to be measured.
::   len - [ByRef Out, Optional] Name of the variable that receives the measured
::         length. If omitted, the result is assigned to variable len.
::   Variable names must be passed unquoted.
:: Strings of up to 8191 characters are supported.
      %== ! -> exclamation mark, # -> caret ==%     FOR /F "TOKENS=1-3" %%! IN (
                                                        "! ! ^ ^^^! . ^!=^!^^^^"
                                         ) DO FOR %%H IN (FEDCBA9876543210) DO ^
set strlen=^
%==% for /f %%? in ("%%! '") do for %%. in (1 2) do if %%.==2 (^
%=  =% for /f "tokens=1,2" %%1 in ("%%!$args%%! len") do^
%=  =% if not defined %%1 (if %%? lss ' endlocal)^&(set /a %%2=0) else^
%=  =% (if : neq :%%!%%1:~4095%%! (set $=1%%!%%1:~4096%%!)^
%=   =% else set $=0%%!%%1%%!)^&^
%=  =% set $Scale=^
%=   =%%%!$:~256%%#,1%%!%%!$:~512%%#,1%%!%%!$:~768%%#,1%%!%%!$:~1024%%#,1%%!^
%=   =%%%!$:~1280%%#,1%%!%%!$:~1536%%#,1%%!%%!$:~1792%%#,1%%!%%!$:~2048%%#,1%%!^
%=   =%%%!$:~2304%%#,1%%!%%!$:~2560%%#,1%%!%%!$:~2816%%#,1%%!%%!$:~3072%%#,1%%!^
%=   =%%%!$:~3328%%#,1%%!%%!$:~3584%%#,1%%!%%!$:~3840%%#,1%%!%%H^&^
%=  =% for %%_ in (%%!$Scale:~15%%#,1%%!) do set $=%%!$:~%%#,1%%!^
%=   =%%%!$:~0x%%_00%%!%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H^
%=   =%FFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCC^
%=   =%BBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAA99999999999999998888888888888888^
%=   =%7777777777777777666666666666666655555555555555554444444444444444^
%=   =%3333333333333333222222222222222211111111111111110000000000000000^&^
%=  =% for %%- in (%%!$:~%%#,1%%!%%_%%!$:~513%%#,1%%!%%!$:~257%%#,1%%!) do^
%=  =% (if %%? lss ' endlocal)^&set /a %%2=0x%%-^
%==% ) else (if %%? gtr ' setlocal enabledelayedexpansion)^&set $args=
goto :eof
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
Four-digit hex value evaluated, like 0xWXYZ with ...
W - !$:~,1! = multiples of 4096 [0,1]
X - %%_ = !$Scale:~15,1! = multiples of 256 [0..F]
Y - !$:~513,1! = multiples of 16 [0..F]
Z - !$:~257,1! = multiples of 1 [0..F] ([1..F] if WXY is 000, since empty strings are excluded early)

pieh-ejdsch
Posts: 257
Joined: 04 Mar 2014 11:14
Location: germany

Re: strLen boosted

#79 Post by pieh-ejdsch » 16 Aug 2025 14:42

An if else statement for measuring length was replaced with a for loop,
resulting in a tiny improvement of 3...4 percent.
Other versions are also available in the test code #65.
Since these are only bloated and do not achieve any performance gains,
I did not use them.

Code: Select all

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:initStrLenAll
:: Computes the number of bytes in a string.
:: %strLen% str len
::   str - [ByRef In] Name of the variable containing the string to be measured.
::   len - [ByRef Out] Name of the variable that receives the measured length.
:: Strings of up to 8191 characters are supported.
    %== ! -> exclamation mark, # -> caret ==%     @FOR /F "tokens=1-3" %%! IN (
                                              "! ! ^ ^^^! . ^^^^") DO @^
set strLen=@^
for /f %%? in ("%%! '") do @for %%. in (1 2) do @if %%.==2 ^
 for /f tokens%%#=1-2 %%1 in ("%%!$args%%! len") do^
 @if not defined %%1 (if %%? lss ' endlocal)^&(set /a %%2=0) else^
 for /f tokens%%#=4delims%%#=%%#  %%4 in^
 ("x %%!%%1:~4095,1%%! x%%!%%1:~4095,1%%!x 1 0") do @set $=A%%!%%1:~0x%%4000%%!^&^
 set $Scale=^
%%!$:~256%%#,1%%!%%!$:~512%%#,1%%!%%!$:~768%%#,1%%!%%!$:~1024%%#,1%%!%%!$:~1280%%#,1%%!^
%%!$:~1536%%#,1%%!%%!$:~1792%%#,1%%!%%!$:~2048%%#,1%%!%%!$:~2304%%#,1%%!%%!$:~2560%%#,1%%!^
%%!$:~2816%%#,1%%!%%!$:~3072%%#,1%%!%%!$:~3328%%#,1%%!%%!$:~3584%%#,1%%!%%!$:~3840%%#,1%%!^
FEDCBA9876543210^&^
 for %%3 in (%%!$Scale:~15%%#,1%%!) do @set $=%%!$:~0x%%300%%!^
FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210^
FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210^
FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210^
FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210FEDCBA9876543210^
FFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCC^
BBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAA99999999999999998888888888888888^
7777777777777777666666666666666655555555555555554444444444444444^
3333333333333333222222222222222211111111111111110000000000000000^&^
 for %%$ in (%%4%%3%%!$:~512%%#,1%%!%%!$:~256%%#,1%%!) do^
 @(if %%? lss ' endlocal)^&(set /A %%2=0x%%$^
 ) else (if %%? GTR ' setlocal enabledelayedexpansion)^&set $args=
@goto :eof

aGerman
Expert
Posts: 4743
Joined: 22 Jan 2010 18:01
Location: Germany

Re: strLen boosted

#80 Post by aGerman » 17 Aug 2025 04:04

Hmm, the new code performs slightly worse in my tests. Expanding !%%1:~4095,1! twice might be the reason, although it's a clever way to handle a space at this position.

pieh-ejdsch
Posts: 257
Joined: 04 Mar 2014 11:14
Location: germany

Re: strLen boosted

#81 Post by pieh-ejdsch » 18 Aug 2025 13:16

A version that uses octal numbers.
It's really easy to work with because fewer for variables are needed—four instead of eight.
This also made the code easier to write (don't think it was easy for me...).
So the script is shorter too.
The shift in the first for loop for using the four different variables is, of course, optimal.
IcarusLives uses something like this all the time – I just hadn't quite understood how it works.
The two for loops save one variable creation and only ten variable queries are made instead of seventeen.

Code: Select all

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:initStrLen2v
:: Computes the number of bytes in a string.
:: %strLen% str len
::   str - [ByRef In] Name of the variable containing the string to be measured.
::   len - [ByRef Out] Name of the variable that receives the measured length.
:: Strings of up to 8191 characters are supported.
FOR /F %%! IN  ("! ! ^ ^^^^^^^^^^^!") DO set i=%%!%%1:~03777,1%%!^
 x%%!%%1:~03777,1%%!x&set o=%%!%%1:~0%%4777,1%%! x%%!%%1:~0%%4777,1%%!x^
 %%!%%1:~0%%4377,1%%! x%%!%%1:~0%%4377,1%%!x
set F=FEDCBA9876543210&set $=0000000000000000
   %== ! -> exclamation mark, # -> caret ==%    @FOR /F "tokens=1-3" %%! IN (
                                         "! ! ^ ^^^! . ^^^^") DO ^
set strLen=^
for /f %%? in ("%%! '") do for %%. in (1 2) do if %%.==2 ^
 for /f tokens%%#=1-2 %%1 in ("%%!$args%%! len") do^
 if not defined %%1 (if %%? lss ' endlocal)^&(set /A %%2=0) else^
 for /f "tokens=7,11,15,19delims= " %%4 in (^"^
 %i% %i:3=7% %i:3=13% 17 13 7 3  16 12 6 2  15 11 5 1  14 10 4 0^") do^
 for /f tokens%%#=15delims%%#=%%#  %%3 in (^" %o:4=7% %o:4=6% %o:4=5% %o:*x =%^
 %%44 %%40 %%54 %%50 %%64 %%60 %%74 %%70^") do^
 set $=%%!%%1:~0%%300%%!%F%%F%%F%%F%%F%%F%%F%%F%%F%%F%%F%%F%%F%%F%%F%%F%^
%$:0=F%%$:0=E%%$:0=D%%$:0=C%%$:0=B%%$:0=A%%$:0=9%%$:0=8%^
%$:0=7%%$:0=6%%$:0=5%%$:0=4%%$:0=3%%$:0=2%%$:0=1%%$%^&^
 for %%$ in (0%%300+0x0%%!$:~511%%#,1%%!%%!$:~255%%#,1%%!) do^
 (if %%? lss ' endlocal)^&(set /A %%2=%%$^
 ) else (if %%? GTR ' setlocal enabledelayedexpansion)^&set $args=
for %%i in (i o F $) do set "%%i="
@goto :eof
Test data output

Code: Select all

Check length str1 gtr 8190 X

~~~~~~~~~~~~~~~~~~~~
"2v           TEST"
Delayed ON
Functional test 0 ... 8189 + 8191 with Delayedexpansion ON (create macro)
04.93
04.48
04.48
Delayed OFF
Functional test 0 ... 8189 + 8191 with Delayedexpansion OFF (create macro)
04.50
04.50
04.50

~~~~~~~~~~~~~~~~~~~~
"2w           TEST"
Delayed ON
Functional test 0 ... 8189 + 8191 with Delayedexpansion ON (create macro)
05.05
05.05
05.05
Delayed OFF
Functional test 0 ... 8189 + 8191 with Delayedexpansion OFF (create macro)
05.03
05.04
05.03

~~~~~~~~~~~~~~~~~~~~
"2x           TEST"
Delayed ON
Functional test 0 ... 8189 + 8191 with Delayedexpansion ON (create macro)
05.25
05.25
05.27
Delayed OFF
Functional test 0 ... 8189 + 8191 with Delayedexpansion OFF (create macro)
05.25
05.23
05.24

~~~~~~~~~~~~~~~~~~~~
"2y           TEST"
Delayed ON
Functional test 0 ... 8189 + 8191 with Delayedexpansion ON (create macro)
05.50
05.49
05.50
Delayed OFF
Functional test 0 ... 8189 + 8191 with Delayedexpansion OFF (create macro)
05.39
05.42
05.42

~~~~~~~~~~~~~~~~~~~~
"All      Batch and CMDline"
Delayed ON
Functional test 0 ... 8189 + 8191 with Delayedexpansion ON (create macro)
04.55
04.55
04.56
Delayed OFF
Functional test 0 ... 8189 + 8191 with Delayedexpansion OFF (create macro)
04.56
04.56
04.58
done
Delayed OFF
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
I  x8400 I   all  I   2v   I   2w   I   2x   I   2y   I   2a   I   2b   I   all  I
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
I  8191  I 02.92  I 01.99  I 02.02  I 02.00  I 02.08  I 02.14  I 02.19  I 02.01  I
I  0     I 02.95  I 02.00  I 02.04  I 02.07  I 02.07  I 02.16  I 02.19  I 02.01  I
I  6000  I 03.10  I 02.11  I 02.18  I 02.17  I 02.20  I 02.27  I 02.31  I 02.12  I
I  4000  I 03.04  I 02.06  I 02.13  I 02.14  I 02.17  I 02.24  I 02.25  I 02.09  I
I  4500  I 03.08  I 02.11  I 02.13  I 02.14  I 02.17  I 02.23  I 02.28  I 02.11  I
I  1000  I 02.99  I 02.03  I 02.06  I 02.07  I 02.09  I 02.17  I 02.20  I 02.04  I
I  600   I 02.99  I 02.02  I 02.08  I 02.06  I 02.09  I 02.17  I 02.21  I 02.05  I
I  200   I 02.98  I 02.03  I 02.07  I 02.06  I 02.09  I 02.14  I 02.18  I 02.03  I
I  10    I 02.95  I 02.00  I 02.07  I 02.04  I 02.10  I 02.15  I 02.19  I 02.05  I
I  1     I 02.97  I 02.00  I 02.05  I 02.04  I 02.08  I 02.16  I 02.17  I 02.01  I
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
I  x8400 I   all  I   2v   I   2w   I   2x   I   2y   I   2a   I   2b   I   all  I
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
I  5412  I 03.10  I 02.10  I 02.15  I 02.14  I 02.21  I 02.26  I 02.28  I 02.10  I
I  8026  I 03.17  I 02.17  I 02.21  I 02.22  I 02.30  I 02.28  I 02.33  I 02.16  I
I  2194  I 03.01  I 02.03  I 02.10  I 02.10  I 02.13  I 02.19  I 02.22  I 02.04  I
I  4229  I 03.08  I 02.09  I 02.13  I 02.13  I 02.17  I 02.22  I 02.27  I 02.10  I
I  7246  I 03.13  I 02.15  I 02.17  I 02.20  I 02.21  I 02.30  I 02.33  I 02.16  I
I  690   I 02.99  I 02.03  I 02.06  I 02.06  I 02.11  I 02.14  I 02.20  I 02.03  I
I  5178  I 03.09  I 02.09  I 02.14  I 02.15  I 02.19  I 02.24  I 02.28  I 02.09  I
I  3009  I 03.02  I 02.07  I 02.09  I 02.08  I 02.13  I 02.21  I 02.25  I 02.08  I
I  4674  I 03.08  I 02.09  I 02.14  I 02.12  I 02.16  I 02.24  I 02.27  I 02.07  I
I  800   I 02.97  I 02.02  I 02.05  I 02.03  I 02.10  I 02.17  I 02.22  I 02.03  I
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
I  x8400 I   all  I   2v   I   2w   I   2x   I   2y   I   2a   I   2b   I   all  I
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
I  59    I 02.97  I 02.00  I 02.04  I 02.05  I 02.08  I 02.16  I 02.20  I 02.02  I
I  774   I 02.97  I 02.03  I 02.05  I 02.05  I 02.08  I 02.16  I 02.20  I 02.00  I
I  4284  I 03.06  I 02.08  I 02.14  I 02.14  I 02.16  I 02.24  I 02.26  I 02.08  I
I  6160  I 03.09  I 02.09  I 02.15  I 02.16  I 02.21  I 02.26  I 02.31  I 02.13  I
I  6320  I 03.11  I 02.13  I 02.16  I 02.17  I 02.20  I 02.26  I 02.30  I 02.11  I
I  6950  I 03.11  I 02.16  I 02.15  I 02.16  I 02.20  I 02.25  I 02.29  I 02.13  I
I  3621  I 03.03  I 02.11  I 02.13  I 02.11  I 02.16  I 02.20  I 02.21  I 02.08  I
I  4032  I 03.06  I 02.06  I 02.11  I 02.12  I 02.18  I 02.22  I 02.28  I 02.09  I
I  7713  I 03.15  I 02.14  I 02.19  I 02.18  I 02.23  I 02.25  I 02.29  I 02.14  I
I  4247  I 03.04  I 02.08  I 02.12  I 02.11  I 02.16  I 02.24  I 02.25  I 02.06  I
+--------+--------+--------+--------+--------+--------+--------+--------+--------+
Drücken Sie eine beliebige Taste . . .
It is, of course, astonishing that the functional test runs in less time than with the other versions.
That alone should say enough about the performance.

aGerman
Expert
Posts: 4743
Joined: 22 Jan 2010 18:01
Location: Germany

Re: strLen boosted

#82 Post by aGerman » 19 Aug 2025 10:12

This is now unbelievably fast. Kudos!
However, I admit that I immediately changed the code to get rid of the variables i and o, as they seemed like code obfuscation :lol:

Talking of obfuscation - is there any good reason to prefer ...

Code: Select all

for /f tokens^=15delims^=^  %%3 ...
... to the more readable ...

Code: Select all

for /f "tokens=15 delims= " %%3 ...
I didn't notice any performance degradation.

aGerman
Expert
Posts: 4743
Joined: 22 Jan 2010 18:01
Location: Germany

Re: strLen boosted

#83 Post by aGerman » 20 Aug 2025 15:30

pieh-ejdsch
What if we claim (or define) that control characters are not part of a string? Would that be reasonable?

Code: Select all

::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
:initStrLen
:: Computes the number of resulting UTF-16 code units in a string.
:: %strLen% str [len]
::   str - [ByRef In] Name of the variable containing the string to be measured.
::   len - [ByRef Out, Optional] Name of the variable that receives the measured
::         length. If omitted, the result is assigned to variable len.
::   Variable names must be passed unquoted.
:: Strings of up to 8191 characters are supported.
         %== ! -> exclamation mark, # -> caret ==%  FOR /F "TOKENS=1-3" %%! IN (
                                                        "! ! ^ ^^^! . ^!=^!^^^^"
         %== _ -> backspace ==%                             ) DO FOR /F %%_ IN (
                                             '"PROMPT $H&FOR %%B IN (1) DO REM"'
                                         ) DO FOR %%H IN (FEDCBA9876543210) DO ^
set strLen=^
%==% for /f %%? in ("%%! '") do for %%. in (1 2) do if %%.==2^
%=  =% for /f "tokens=1,2" %%1 in ("%%!$args%%! len") do^
%=    =% if not defined %%1 ((if %%? lss ' endlocal)^&set /a %%2=0)^
%=    =% else for /f "tokens=4,8,12,16 delims=%%_" %%4 in (^"^
%=     =%%%!%%1:~2047,1%%!%%_%%!%%1:~4095,1%%!%%_%%!%%1:~6143,1%%!%%_^
%=     =%17%%_13%%_7%%_3%%_16%%_12%%_6%%_2%%_^
%=     =%15%%_11%%_5%%_1%%_14%%_10%%_4%%_0^"^
%=    =% ) do for /f "tokens=8 delims=%%_" %%3 in (^"^
%=     =%%%!%%1:~0%%7777,1%%!%%_%%!%%1:~0%%7377,1%%!%%_^
%=     =%%%!%%1:~0%%6777,1%%!%%_%%!%%1:~0%%6377,1%%!%%_^
%=     =%%%!%%1:~0%%5777,1%%!%%_%%!%%1:~0%%5377,1%%!%%_%%!%%1:~0%%4377,1%%!%%_^
%=     =%%%44%%_%%40%%_%%54%%_%%50%%_%%64%%_%%60%%_%%74%%_%%70^"^
%=    =% ) do set $=%%!%%1:~0%%300%%!^
%=     =%%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H%%H^
%=     =%FFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCC^
%=     =%BBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAA99999999999999998888888888888888^
%=     =%7777777777777777666666666666666655555555555555554444444444444444^
%=     =%3333333333333333222222222222222211111111111111110000000000000000^&^
%=    =% for %%- in (0%%300+0x0%%!$:~511%%#,1%%!%%!$:~255%%#,1%%!) do^
%=      =% ((if %%? lss ' endlocal)^&set /a %%2=%%-)^
%==% else (if %%? gtr ' setlocal enabledelayedexpansion)^&set $args=
goto :eof
::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
This is still your latest code. The x!var:~N,1!x workaround is removed. Instead I used the backspace character as delimiter because it should never appear in a string and it is easy to generate.

Steffen

Post Reply