Task stack usage?

Started by arhodes19044 April 8, 2005

When a task calls a subroutine, do all the subroutine's local
variables get placed on the Task's stack, or the main stack? I am sure I will find this out shortly, but if I have a task sleep for
a while and then call a few subroutines with no variables being
passed, but with a bunch of local variables in the subs, will I
greatly enlarge my task stack requirement beyond the overhead for the
simple call?

If so, I can make the required variables global. If I have to reserve
the space for those variables anyway, I might as well make the space
available for other subs to use as temporary storage.

-Tony



Tony,

When you call a subroutine as a new task, local variables get placed on
that new task's stack. There are two main methods for communicating
between tasks: global variables (plus perhaps semaphores) and queues.

There is a definite tradeoff between global and local variable storage.
Globals reduce the overall amount of storage that can be allocated to
stacks but can be shared. Local variables are temporary in that they
take up stack space but can then later be freed for another subroutine
call. The overhead for each subroutine call is 9 bytes + parameters +
local variables + stack usage of the subroutine and so on in nested calls.

I don't understand your question about a task sleeping. Each task has
its own stack that is separate and independent. The "main" task also has
a stack made up of the remaining memory after allocating global
variables and task stacks.

Mike

>
> When a task calls a subroutine, do all the subroutine's local
> variables get placed on the Task's stack, or the main stack? > I am sure I will find this out shortly, but if I have a task sleep for
> a while and then call a few subroutines with no variables being
> passed, but with a bunch of local variables in the subs, will I
> greatly enlarge my task stack requirement beyond the overhead for the
> simple call?
>
> If so, I can make the required variables global. If I have to reserve
> the space for those variables anyway, I might as well make the space
> available for other subs to use as temporary storage.
>
> -Tony
>




SOrry about the sleeping thing. It is extraneous information. The
fact that it calls sleep is not important to the overall function of
the task, not to its stack usage.

Operationally: The task will loop infinitely and spend most of its
time sleeping {Call sleep(512)}, then will wake up and execute a few
subroutines, then do back to a big sleep.

As for the stack: Since the task runs infinitely, its task stack
will never get released. Therefore I might as well make the local
variables in the called subroutines globals so that other subs can
share them if the opportunity arises.

I hate making variables global. I spent many years of C programming
breaking the lazy habit I developed when programming very early
BASIC (actually at that time all variables were "global" because the
subroutines were rudimentary and had no provision for local vars).
Now I am doing it to conserve memory!!

-Tony
--- In basicx@basi..., Mike Perks <basicx@a...> wrote:
> Tony,
>
> When you call a subroutine as a new task, local variables get
placed on
> that new task's stack. There are two main methods for
communicating
> between tasks: global variables (plus perhaps semaphores) and
queues.
>
> There is a definite tradeoff between global and local variable
storage.
> Globals reduce the overall amount of storage that can be allocated
to
> stacks but can be shared. Local variables are temporary in that
they
> take up stack space but can then later be freed for another
subroutine
> call. The overhead for each subroutine call is 9 bytes +
parameters +
> local variables + stack usage of the subroutine and so on in
nested calls.
>
> I don't understand your question about a task sleeping. Each task
has
> its own stack that is separate and independent. The "main" task
also has
> a stack made up of the remaining memory after allocating global
> variables and task stacks.
>
> Mike
>
> >
> > When a task calls a subroutine, do all the subroutine's local
> > variables get placed on the Task's stack, or the main stack?
> >
> >
> > I am sure I will find this out shortly, but if I have a task
sleep for
> > a while and then call a few subroutines with no variables being
> > passed, but with a bunch of local variables in the subs, will I
> > greatly enlarge my task stack requirement beyond the overhead
for the
> > simple call?
> >
> > If so, I can make the required variables global. If I have to
reserve
> > the space for those variables anyway, I might as well make the
space
> > available for other subs to use as temporary storage.
> >
> > -Tony
> >



Tony,

Just a few points of clarification.

>
> As for the stack: Since the task runs infinitely, its task stack
> will never get released. Therefore I might as well make the local
> variables in the called subroutines globals so that other subs can
> share them if the opportunity arises.

Task memory is not released - there is no such thing as memory
management in BasicX other than the compiler statically assigning blocks
of memory for different data structures such as global variables,
queues, and stacks. That's why you can write over the memory so easily.

You can start a new task and reuse the stack from another task. If that
task is still running we all know the result - lockup. If that task has
terminated then all well and good except that you may still run off the
end of stack and overwrite something else.

>
> I hate making variables global. I spent many years of C programming
> breaking the lazy habit I developed when programming very early
> BASIC (actually at that time all variables were "global" because the
> subroutines were rudimentary and had no provision for local vars).
> Now I am doing it to conserve memory!!
Again there is a tradeoff of global (no reuse) and local (reuse of
memory). You may have to "play some" to figure out the best comprises
for your particular program.

Mike


> ... a tradeoff of global (no reuse) and local (reuse of memory)...

Your perspective is interesting, Mike. I think of globals as common or
reusable vars. You think of locals as reused memory.

There might be a philosophy there. Tom
Tom Becker
--... ...--
GTBecker@GTBe... www.RighTime.com
The RighTime Clock Company, Inc., Cape Coral, Florida USA
+1239 540 5700


Let me clarify Tom - I mean reuse of memory. Globals once allocated
cannot be freed.

If you declare everything as a global there is no space left for the
stack. If you declare everything as a local, you can potentially call
subroutines all day and be fine.

This discussion is only relevant when you are tight on memory and
running out of space for everything. Then its a matter of performance
tuning and one aspect of that is trading off globals versus locals. For
example a global named TEMP may be valuable as a reusable temporary
calculation space that can be reused by multiple nesting levels of
subroutines instead of each allocating space for a local copy of TEMP.

We have discussed before about micro tuning of expressions (RPN
reordering) and macro tuning of functions (see part 4 of my articles) as
other ways to reclaim valuable RAM. The bxDism program may also be of help.

Mike
http://home.austin.rr.com/perks/basicx/Articles/

> > ... a tradeoff of global (no reuse) and local (reuse of memory)...
>
> Your perspective is interesting, Mike. I think of globals as common or
> reusable vars. You think of locals as reused memory.
>
> There might be a philosophy there. > Tom




> ... Globals once allocated cannot be freed...
> ... only relevant when you are tight on memory...

Yes, the programmer should use globals judiciously, but they are faster
than pushing a new local var and take no additional RAM when reused.
Deciding where to put a var might include speed, not just space. Tom
Tom Becker
--... ...--
GTBecker@GTBe... www.RighTime.com
The RighTime Clock Company, Inc., Cape Coral, Florida USA
+1239 540 5700



--- In basicx@basi..., "Tom Becker" <gtbecker@r...> wrote:
> > ... Globals once allocated cannot be freed...
> > ... only relevant when you are tight on memory...
>
> Yes, the programmer should use globals judiciously, but they are
> faster than pushing a new local var and take no additional RAM when
> reused.

Referencing a global variable is only marginally faster than
referencing a local variable. In the former case, the compiler has
the absolute address, in the latter case the compiler knows the
relative address and it is converted into an absolute address at run
time by adding the relative address to the stack frame address.
Since this is done in the VM code (as opposed to in User code), that
addition takes 2 CPU cycles which, compared to the pcode instruction
loop time, is insignificant. (Simple pcode instructions execute in
about 100 CPU cycles, more or less, counting the time to fetch them
from external EEPROM which is probably pipelined.)

Regarding "pushing a new local var" it doesn't take any more time to
allocate 3 local variables than it does to allocate 2 local
variables. The allocation is done en masse, when the routine is
invoked, by adding a value to the User SP.

The reference above to the time required to execute simple pcode
instructions is my estimate for straight line execution. Branches
take a lot longer due to the need to restart the fetch cycle. This
takes about 128 CPU cycles. The improved performance of the BX-24p
is probably largely due to halving the time required to read from
external EEPROM by using the 2x SPI mode.

Don



> Regarding "pushing a new local var" it doesn't take any more time to
> allocate 3 local variables than it does to allocate 2 local
> variables. The allocation is done en masse, when the routine is
> invoked, by adding a value to the User SP.

Hmmm. I'll need to restudy the code that started my custom of using
globals preferentially. I believe I can show you code that is
significantly faster using globals. Tom
Tom Becker
--... ...--
GTBecker@GTBe... www.RighTime.com
The RighTime Clock Company, Inc., Cape Coral, Florida USA
+1239 540 5700



--- In basicx@basi..., "Tom Becker" <gtbecker@r...> wrote:
> I believe I can show you code that is significantly faster using
> globals.

Here is some test code:

Dim b as Byte
Sub Main()
Dim lb as Byte
lb = 1
b = 1
Call PutPin(12, bxOutputLow)
Do
Register.PortC = Register.PortC Xor 1
Loop
End Sub

This first test case has a loop execution time of 90.4uS and provides
the baseline execution time. If the toggling line is changed to use
the global variable b:

Register.PortC = Register.PortC Xor b

the loop execution time is 95.2uS. If it is changed to use the local
variable lb

Register.PortC = Register.PortC Xor lb

the loop execution time is 95.8uS.

The same tests on a BX-24p yield 66.3uS, 70.2uS and 71.1uS.

All times were measured using a logic analyzer with a sample interval
of either 0.2uS (for the old BX-24) or 0.1uS (for the BX-24p).

The number of bytes of code generated in each case is identical. The
only difference is that the first case pushes an immediate value, the
second pushes from an absolute address and the third pushes from a
relative address.

On the old BX-24, the difference between the absolute and relative
cases amounts to about 4 CPU cycles. I'm not sure why that is the
case since a 16-bit add only takes 2 cycles assuming that you have
the values in registers already. If the VM code is written so that
the stack frame pointer needs to be loaded from RAM that would
require an additional 4 cycles.

Don