After searching for strlen threads, this one seems the most appropriate place to put this. Building on all previous work in this thread (so very little here is my work), here's my take on strlen:
Code: Select all
:strlen
( setlocal enabledelayedexpansion & set /a "}=0"
if defined %~1 (
for %%# in (4096 2048 1024 512 256 128 64 32 16) do (
if "!%~1:~%%#,1!" neq "" set "%~1=!%~1:~%%#!" & set /a "}+=%%#"
)
set "%~1=!%~1!10000000000000000FEDCBA987654321" & set /a "}+=0x!%~1:~16,1!!%~1:~32,1!"
)
)
endlocal & if "%~2" neq "" set /a "%~2=%}%" & exit /b
I apologize for it still being a little scrunched up but it should be somewhat readable.
Via tests on my computer, this came out as 1% to 17% (for short to long strings) faster than strLenO_dipstick_powers which was the previous fastest when I tested everything in this thread. For short strings, the difference may fall to random variation and timing inaccuracy, but there were nice savings in speed with massive strings.
Since we're using setlocal anyway, I don't copy the string to a new env var and instead just use the setlocal copy directly which saves some time; it's as much as ~17% faster for a maximum length string (on my computer). Since I'm not sticking a safety character onto the front or back of the string, the ''dipstick'' has to be slightly bulkier to account for a potential 16 character leftover and I use an
if defined to check for a zero length string.
I didn't get speed differences between
if not a==b and
if a neq b so I left it as
neq based on precedent set in this thread.
Almost all of the speed improvement came from the lack of the intial string copy, so this could surely be improved upon; coping with the loss of the safety character is primarily what needs to be worked around to improve it.
Edit - Yes, it's obvious that the
if defined will have bad behavior if the call sends an empty first argument. Throw a
if "%~1" neq "" before the
if defined %~1 for safety if it's a concern. Hm, thinking on it, a pre-setlocal abort if argument 1 is empty or the var isn't defined might be worth it (
Edit 3 - It's not worth it; another block of code that has to be parsed separately slows it down too much. Now I see why things turned out the way they did. -_-).
Edit 2 - There are some minor structural flaws in the return that would also choke on bad arguments. Guess this needs more work. Regardless, I think there's merit in not initially copying the string data.
Edit 4 - Ok, maybe this instead:
Code: Select all
:strlen
( setlocal enabledelayedexpansion & set /a "}=0"
if "%~1" neq "" if defined %~1 (
for %%# in (4096 2048 1024 512 256 128 64 32 16) do (
if "!%~1:~%%#,1!" neq "" set "%~1=!%~1:~%%#!" & set /a "}+=%%#"
)
set "%~1=!%~1!0FEDCBA9876543211" & set /a "}+=0x!%~1:~32,1!!%~1:~16,1!"
)
)
endlocal & set /a "%~2=%}%" & exit /b
Set actually gives us good feedback in the case of an empty
%2. It honestly seems like a waste to sanity check it. I'd also like to note this function doesn't spit out a garbage return (often 4100 in previous implementations) on a failed env var creation (due to oversized input env vars) and can list string length up to 8189 (and reports 8189 for any string longer than 8189, at least in the test framework). Oh, and don't pass this function
cmdcmdline without first ''stabilizing'' it (
set cmdcmdline=%cmdcmdline%).
%cmdcmdline% is actually the reason I started looking at a strlen function.
Queue