out of memory?? but how i should have plenty

Discussion specific to the DIP and TQFP packaged ZX devices like the ZX-40, ZX-44, ZX-32 and ZX-328 series. The differences between these devices is primarily the packaging and pinout so most issues will apply to all devices.
Post Reply
cadillackid
Posts: 35
Joined: 22 August 2009, 16:34 PM

out of memory?? but how i should have plenty

Post by cadillackid »

im getitng this error compiling for my 328n... I thought I had 2048 bytes of RAM... and yet iots telling me im out of task stack space...

Code: Select all

Warning(13): task stack for "Main" is too small, 223 bytes needed, 134 bytes available
No errors.  Target device: ZX328n, Code: 21224 bytes, RAM: 1402 bytes, Persistent memory: 32 bytes
I have my task stacks for 2 tasks set at the mimimums as suggested by the compiler.. I thought I should have 600 or more RAM bytes free that main could use..
-Christopher
cadillackid
Posts: 35
Joined: 22 August 2009, 16:34 PM

Post by cadillackid »

I changed my option heapsize and was able to clear the warning but not sure if this was a good idea or not??

the program seems to be running normally now..
-Christopher
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

cadillackid wrote:I changed my option heapsize and was able to clear the warning but not sure if this was a good idea or not??
It may or may not be - it depends on your program. The heap is used for all computed string values (as opposed to constant string values) so if you have a lot of computed string values that have a long lifetime you may run out of heap space.

To clarify the difference between computed and constant string values, consider the following.

Code: Select all

Dim s1 as String, s2 as String
Sub Main()
  s1 = "Hello, world!" & Chr(&H0d) & Chr(&H0a)
  Call foo()
  s2 = Mid(s1, 2, 5) & "xxx"
End Sub

Sub foo()
End Sub
In this example, the storage for the characters of 's1' is in Flash memory because it is constant and computable at compile-time. In contrast, the storage for the characters of 's2' is in the heap because it isn't constant and computable at compile-time. Note, however, that if you comment out the call to foo() then 's2' becomes a constant string, too.

The reason for difference with the call to foo() present and not present is because 's1' is a module-level string that is subject to being changed by foo(). (In this particular case, the compiler could make the observation that foo() does not modify 's1' but in the general case the compiler could not make such a determination.)

There is a way that you can determine empirically how much heap space you need. This is done by thoroughly exercising your application (so that all execution paths are taken) and then, at some point, making a call to System.HeapHeadRoom(). The value returned is the number of bytes in the heap that have never been used. The maximum heap space used upt to that time can be computed by subtracting the value returned by System.HeapHeadRoom() from the value returned by System.HeapSize().

Note that heap space is also used by calls to System.Alloc() and, in native mode, by operations that update ProgMem variables.
- Don Kinzer
cadillackid
Posts: 35
Joined: 22 August 2009, 16:34 PM

Post by cadillackid »

so the fact that I use a lot of variables could be a reason why I use so much space for such a small program.... for instance im likely to do this:

Code: Select all

sub main()
call foo1
call foo2
end sub


sub foo1()

dim x as integer

for x =1 to 20

... do stuff here

next
end sub

sub foo2()

dim y as integer

for y=1 to 50
... do stuff here
next
end sub
so I never re-used a variable.. im guessing i couldve used x in both cases since foo1 and foo2 never get called at the same time? and saved memory perhaps?

-Chris
mikep
Posts: 796
Joined: 24 September 2005, 15:54 PM

Post by mikep »

An integer only takes 2 bytes so removing or reusing one isn't going to help much. In the code you provided both x and y are declared as "stack variables" i.e. local to the routine and they only exist for the lifetime of the routine. Renaming y to x in this case does not save anything. See section 3.1 in the documentation on "Scope and Lifetime".

What is more interesting are the variables you declare at the module level i.e. outside of all of the routines.

To find out much RAM you are using, first look at the MAP file or append it as a file here. It only contains high level information for nativre mode devices. To find the next level of detail you need to add the option "--keep-files" to your project. Under the zxTempDir you will find a file of type ".sym" that is generated by the compiler. Again post that file here so we can understand what is using up all the RAM in your program.
Mike Perks
dkinzer
Site Admin
Posts: 3120
Joined: 03 September 2005, 13:53 PM
Location: Portland, OR

Post by dkinzer »

cadillackid wrote:so the fact that I use a lot of variables could be a reason why I use so much space for such a small program
To get a grasp on how RAM is used, you need to understand how RAM is partitioned and how space is allocated for different types of variables.

Consider the diagram below (excerpted from the ZBasic Language Reference Manual). The top portion represents the data items that are statically allocated, meaning that they are defined at the module level, i.e. defined outside of any procedure, or defined within a procedure with the Static attribute. The variables in this space exist for the entire time that the application is running. Note that the stacks for the tasks other than Main() are generally statically allocated so they "live" in this space, too.

The middle portion of RAM is dedicated to the task stack for Main(). (As a side note, the task stacks are used beginning from low end for VM devices but they are used beginning from the high end for native devices.) When you define a variable within a procedure (and it doesn't have the Static attribute), space for that variable is allocated on the calling task's stack. The space is "in use" only for the duration of the execution of that procedure and it re-used when the next procedure is invoked. Note, however, that if A calls B which calls C, the local variables for all three procedures exist on the stack while C is executing. When the compiler performs stack use analysis, it takes these details into consideration.

The bottom portion of the diagram is the heap (which grows from the end of RAM toward lower addresses). As described in an earlier post, the heap is used primarily for storing the characters of run-time computed strings. It is also used by System.Alloc() and when writing ProgMem variables on ZX devices which use internal Flash memory for program storage (i.e. they do not use an external EEPROM).

As described earlier, in a VM mode device, the Main() task stack grows from lower address toward higher addresses and the heap grows from higher addresses toward lower addresses. This fact allows the Main() task stack and the heap to share the space above the statically allocated section. As long as neither grows too large, the program will execute without problem.

In contrast, in a native mode device the Main() task stack grows from higher addresses toward lower addresses. This is an artifact of the way that the hardware stack is implemented in the AVR devices (and most other microcontrollers, too) and it cannot be changed. This fact means that the heap must be given a specific size when the program begins execution. By default, the heap is 512 bytes meaning that the Main() task stack begins 512 bytes from the end of RAM. Depending on your application, this may be more or less than is actually needed. If it is more than is needed RAM is "wasted" and if it is less than needed the application won't run correctly (the primary symptom will be that some string values are zero-length when they shouldn't be).

There are some files produced during compiling/linking that will help you understand how your RAM is being used. For VM mode devices, you can add the --map option to the .pjt file. The content of this file will show all statically allocated variables, the stack use of all procedures and the minimum stack requirement for all tasks.

For native mode devices, the information is in two places. The back-end linker produces a .sym file that shows the address assignment of all procedures and statically allocated variables. If the --map option is present, the .map file will contain information about the stack use of all procedures and the minimum stack requirement for all tasks.
Attachments
ram_allocation.jpg
ram_allocation.jpg (29.37 KiB) Viewed 10262 times
- Don Kinzer
cadillackid
Posts: 35
Joined: 22 August 2009, 16:34 PM

Post by cadillackid »

I can see from this ive been going about things a little wrong...

I tend to define all my variables in the beginning of the module even though some of them may only be used in one routine...

so I really should only define those variables in the declarations part of a module that need to pass data from one subroutine to another or that need to be looked at by more than one routine....

so that way when that routine is not executing that vairable space is returned to available RAM... rather than just sitting there using up space for no reason..
right now I have this

Code: Select all



Private Const pulsestreamLength as Integer = 164
private spulse(1 to (pulsestreamlength)+8) as new unsignedinteger
Private pulses(1 to pulsestreamLength) as New UnsignedInteger
private tempset as byte

thats in the beginning of my module...

however the only routine that actually makes use of that is the routine called unitcom. everytime unitcom is run that data is overwritten and doesnt need to be passed in those variables to any other routine or read by any other...

so i'd probably be better off:

Code: Select all


private sub unitcom()

dim pulsestreamLength as Integer

pulsestreamlength = 164

dim spulse(1 to (pulsestreamlength)+8) as new unsignedinteger
dim pulses(1 to pulsestreamLength) as New UnsignedInteger
dim x



for x=1 to 8
   if getbit(tempset,x)=1 then
       .... yada yada
   else
       ...  yada yada
   end if
next
end sub

since tempset is used by more than one routine id keep it in the module declarations, but the "pulse" related variables could go in the routine level, thus throwing that ram back to the pool when say "onewire" is executing because.. "unitcom" and "onewire" never execute at the same time....

-Christopher
Post Reply