CALL me, or better avoid call

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Message
Author
jeb
Expert
Posts: 917
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: CALL me, or better avoid call

#16 Post by jeb » 25 Oct 2011 14:30

Now, some months later ...

I found the cause why the call set is so slow, and why you should better avoid it.

Code: Select all

@echo off
setlocal
(
   echo echo *** External batch, parameters are '%%*'
) > set.bat
set "var="
call set var=hello
set var


Obviously it always searches for a batch/exe/ececutable command named set.

So call is not only slow it's also unsafe, as you can't know if it works.

jeb

dbenham
Expert
Posts: 2288
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: CALL me, or better avoid call

#17 Post by dbenham » 25 Oct 2011 15:43

:shock: WOW - That certainly is unexpected behavior. I checked and you get the same behavior if executed from the command line.

It took me a while to figure out your point. I modified your test script to make the point more obvious: Invoking an internal command directly ignores any files of that name that happen to be lying around. But CALLing an internal command fails if an executable file exists with the same name. (I fixed and improved this example after jeb's next post below)And fixed this once again

Code: Select all

@echo off
setlocal
(
   echo @echo *** External batch %%~nx0, parameters are '%%*'
) > SET.BAT
copy set.bat SET2.BAT >nul

set "varCall="
set "varDirect="

set varDirect=goodbye
call SET varCall=hello
call :SET :label ignores files

set varCall
set varDirect

echo(
call SET2 Testing call to SET2
call :SET2 Testing call to :SET2

echo(
call ECHO Testing call to ECHO
call :ECHO Testing call to :ECHO

echo(
call UNIQUE_CALL Testing call to UNIQUE_CALL
call :UNIQUE_CALL Testing call to :UNIQUE_CALL

del set.bat
del set2.bat

goto UNIQUE_GOTO

exit /b

:UNIQUE_GOTO
echo GOTO UNIQUE_GOTO worked without the colon
exit /b

:SET
:SET2
:ECHO
:UNIQUE_CALL
echo Called label %0, parameters are '%*'
exit /b

Output:

Code: Select all

*** External batch SET.BAT, parameters are 'varCall=hello'
Called label :SET, parameters are ':label ignores files'
Environment variable varCall not defined
varDirect=goodbye

*** External batch SET2.BAT, parameters are 'Testing call to SET2'
Called label :SET2, parameters are 'Testing call to :SET2'

Testing call to ECHO
Called label :ECHO, parameters are 'Testing call to :ECHO'

'UNIQUE_CALL' is not recognized as an internal or external command,
operable program or batch file.
Called label :UNIQUE_CALL, parameters are 'Testing call to :UNIQUE_CALL'
GOTO UNIQUE_GOTO worked without the colon

This also helps explain why calling a :label can be faster than calling a command. The command wastes time scanning all directories in PATH for a (hopefully) non-existent executable file. But the call to a :label immediately looks for the label without bothering to scan for a file.

Summary of CALL processing sequence from within a batch file:

A) CALL TEXT
- 1 - Look for executable file named TEXT
- 2 - Try TEXT as an internal command

B) CALL :TEXT
- 1 - Look for label named :TEXT

Unrelated) GOTO :LABEL (with colon) and GOTO LABEL (without colon) both work the same. But CALL LABEL (without colon) does not work.

jeb wrote:So call is not only slow it's also unsafe, as you can't know if it works.
Especially if you fail to delete that nasty SET.BAT that was created in your example. :lol:


Dave Benham
Last edited by dbenham on 25 Oct 2011 21:04, edited 3 times in total.

jeb
Expert
Posts: 917
Joined: 30 Aug 2007 08:05
Location: Germany, Bochum

Re: CALL me, or better avoid call

#18 Post by jeb » 25 Oct 2011 16:29

dbenham wrote:Summary of CALL processing sequence from within a batch file:

A) CALL TEXT
- 1 - Look for executable file named TEXT
- 2 - Look for label named :TEXT
- 3 - Try TEXT as an internal command


I can't confirm that on my Vista machine.

I only got
- 1 - Look for executable file named TEXT
- 3 - Try TEXT as an internal command

I checked it with

Code: Select all

@echo off
call myLabel
exit /b

:myLabel
echo This is the function %0
exit /b


Just fails with "myLabel can't be found"

I'm thinking about to modify the internal command in a way, so the call detects immediately that it can't be an external file.

Something like (these one fails)
call "echo" fast
or
call (echo) fast
or
call ==(echo;;) fast

Currently I haven't found a solution :(

jeb

dbenham
Expert
Posts: 2288
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

Re: CALL me, or better avoid call

#19 Post by dbenham » 25 Oct 2011 17:28

jeb wrote:
dbenham wrote:Summary of CALL processing sequence from within a batch file:

A) CALL TEXT
- 1 - Look for executable file named TEXT
- 2 - Look for label named :TEXT
- 3 - Try TEXT as an internal command


I can't confirm that on my Vista machine.

I only got
- 1 - Look for executable file named TEXT
- 3 - Try TEXT as an internal command
Oops! My test code was truncated. Plus while driving home I realized I was missing one critical test that led to what I now know was an incorrect conclusion. I've fixed and improved my prior post and corrected my conclusions.

addendum:
Damn - I still got it wrong on my 2nd try :oops:
CALL LABEL without colon does not work at all. Only GOTO LABEL without colon works.

I edited the prior post yet again. :x


Dave Benham

miskox
Posts: 335
Joined: 28 Jun 2010 03:46

Re: CALL me, or better avoid call

#20 Post by miskox » 03 Dec 2015 02:32

jeb wrote:Perhaps it is slow, as it always creates a new variable context and has to copy all variables.


So it creates environment stack (or how can I call it) each time CALL is used. Can we someshow see the size of this memory usage? I think (but still have to make some tests) that this is visibile in nonpaged memory which really decreases after few hours - the problem I had some time ago with memory leak (see viewtopic.php?f=3&t=5495) was caused by ESET anti virus. Later I had similar problems with nonpaged memory for which I think is caused by too many CALLs. I am using many CALLs (probably some 100,000 calls I would say).

- ~25,000 records
- each record is processed thru some CALLs (five or so - I would have to check) so I guess this really makes a huge environment variable stack. But it looks like this nonpaged pool is freed after few hours - still needs confirmation.

Is it better to use GOTO if possible (I don't think I might be able to use delayedexpansion)?

Saso

Ed Dyreen
Expert
Posts: 1569
Joined: 16 May 2011 08:21
Location: Flanders(Belgium)
Contact:

Re: CALL me, or better avoid call

#21 Post by Ed Dyreen » 03 Dec 2015 16:31

miskox wrote:Is it better to use GOTO if possible (I don't think I might be able to use delayedexpansion)?
The problems described here do not apply for labels. Because labels must always start with a ':' sign.. Why would call try to look for a file :TEXT only to discard it later as an invalid filename. Let's suppose it does look for a file :TEXT. Then I would expect call to say :TEXT IS NOT RECOGNIZED AS AN INTERNAL COMMAND OR BATCH FILE. But if I call an undefined label :TEXT the error I get is LABEL CAN'T BE FOUND. A disk monitoring tool should be used to confirm or disprove my theory.

Code: Select all

@echo off

call label2

echo.oops
pause

:label2
echo.ok
pause
Remember that call uses a call stack and accepts parameters and returns an exitcode, goto does neither of those things. If my theory is correct then trying to emulate the call behavior with goto is silly, because then you'll have to emulate the return which is exactly what the call stack is for.. From analysis of my own programs using a call stack that goes deeper than lets say 10 calls, usually indicates bad program design ( an exception to this rule is the recursive function ).

jfl
Posts: 109
Joined: 26 Oct 2012 06:40
Location: Saint Hilaire du Touvet, France
Contact:

Re: CALL me, or better avoid call

#22 Post by jfl » 10 Dec 2015 10:56

About trying to emulate calls with gotos, I just found this funny trick using a %call% macro using only gotos: :)

Code: Select all

@echo off
setlocal EnableExtensions EnableDelayedExpansion

set "^!=^^^^^^^!"   &:# Define a %!%DelayedExpansion%!% variable
set "call= <nul>nul verify & for %%$ in (1 2) do if %%$==2 (for /f "tokens=1,2,*" %%/ in ("%!%ARGS%!%") do set RETURN_TO=:%%call%%%%/&set ARGS=%%1&goto %%0) else set ARGS="

call :proc arg1 "arg 2"
echo Back from normal call

:# First emulated call using our %call% macro
<:%call%#1 :proc arg3 "arg 4"
echo Back from emulated call #1

:# Again with a distinct call counter
<:%call%#2 :proc arg5 "arg 6"
echo Back from emulated call #2

goto :eof

:proc
echo This is proc. ARGS=%*%ARGS%
goto %RETURN_TO% :eof

outputs

Code: Select all

This is proc. ARGS=arg1 "arg 2"
Back from normal call
This is proc. ARGS=arg3 "arg 4"
Back from emulated call #1
This is proc. ARGS=arg5 "arg 6"
Back from emulated call #2

The %call% macro works by using jeb's technique described in this post. The idea is that the call is itself a return label.
The limitation being that the return label must be unique, which is obtained by appending a unique #N suffix immediately behind each %call%.

The arguments %1 %2 ... are not set, although it's possible to get them through an ARGS variable as shown in the sample code.

Finally the return at the end of the routine works with a real call and an emulated %call%, because goto will silently jump to the first label if it's followed by two labels.

Jean-François

Post Reply