Topic : The GFA-Basic Compendium
Author : GFA Systemtechnik GmbH
Version : GFABasic.HYP v2.98 (12/31/2023)
Subject : Documentation/Programming
Nodes : 899
Index Size : 28056
HCP-Version : 3
Compiled on : Atari
@charset : atarist
@lang :
@default : Document not found
@help : Help
@options : +g -i -s +z
@width : 75
@hostname : STRNGSRV
@hostname : CAB
@hostname : HIGHWIRE
@hostname : THING
View Ref-FileCompiler Option $M
by
Rev 5 1/18/2022
_____________________________
What does compiler option $m actually do? In short it limits the amount of ram
a compiled program will use. It causes a compiled program to allocate only what
it needs from the start.
How does $m differ from RESERVE? A typical compiled program eats all available
ram minus 16384kb, then RESERVE can be used to free some of it.
Why does this matter? Firstly it should be stated that desk accessories must
never consume all available ram, so $m is required. If a desk accessory did eat
all ram, I assume the boot process is going to halt at some point. That would
be bad, plus desk accessories are suppose to co-exist along with other
applications. Another scenario where $m is very important is multitasking. A
single application consuming all available ram in a multitasking environment,
pretty much is a show stopper.
What's actually stored in this block of memory allocated with compiler option
$m? That is a very good question because none of the GFABASIC manuals clearly
state that. The following list should shed some light on that.
Arrays of all types, the array data and the array descriptor.
Strings, the string data and the string descriptor.
Examples: DIM list$(8),line%(16),char|(128)
name$="GFABASIC"
What's not stored in this block of memory? All other variables that are not
arrays or not strings. These are stored in the BSS section of the program.
Examples: version#=3.6
size%=2048
flag!=FALSE
mask|=&HF
Note: INLINE and DATA are stored in the DATA section of the program.
So how does one calculate the value for compiler option $m? Yet again another
good question. Even I have had trouble with this in the past and many times its
simply trial and error. The larger the program, the more arrays and the like,
the harder it becomes to calculate.
GFABASIC manages arrays and strings with something called a descriptor. I'm
not going to explain how these work, but know that they are exactly 6 bytes.
There are other bits of information used along with the descriptor. This
information breaks down like so.
Bytes Description
6 array or string descriptor
(for string arrays, each index has its own string descriptor)
4*? 1st dimension (if more than one, 4 times the number of dimensions)
4 back-trailer
4 block size
Note: Strings are also stored on an even address. If the length of the string
is odd then a byte is wasted for alignment.
As you can see by the list above it could be rather tricky and/or time
consuming to calculate the exact ram required for each and every array in your
program! To make matters worse, strings and string arrays tend to be dynamic
and every one a different length, then add to that you might change them while
its running.
Since I just mentioned that strings are dynamic, we are going to take some time
to explain how GFABASIC manages this block of memory. It uses a classic
technique called garbage collection to manage the strings dynamically as they
change. When a string is reassigned its old contents is marked as unused, but
remains and a new section of the memory block is used. Eventually, after enough
string reassignments occur, the memory block fills up and quite literally
there's no more space left. This is when a garbage collection occurs. All the
strings marked unused are removed and the ones in use are shuffled around to
free up some space. This is why the address of a string might change! I suspect
this works like this for speed reasons, if a garbage collection occurred at
every string reassignment it would surely slow down the program. Error #8
occurs when the garbage collection fails to free up enough space for a new
string assignment or an array DIMension.
I have a coffee mug that says "I love computers! I can sit and watch them do my
work for hours!" What if GFABASIC could help determine the value of compiler
option $m? Who knows better exactly how much ram is being used at any given
moment? The answer of course is GFABASIC itself. This got me thinking and so
what I'm going to suggest is a way for your program, plus a few added lines of
code, can help assist you with this task.
I consider this method a debugging only situation and thus you never want to
leave this sort of code in a finished program. I suggest you copy your source
code to something like "test.gfa" or "debug.gfa" and leave it in the same
directory as the original, so the program can be tested and still find any
support files it might need. The editor handles memory differently and new
variables can be introduced on the fly, they cannot be stored in the BSS
section, thus this debugging method will require you to compile a copy of your
source code. When the debugging is done the copy can be deleted which is easier
than trying to remember which lines of code was added for debugging.
This is not going to involve any magic or peeking around in the libraries
hidden memory, no sir, we are going to use only commands provided by the
language. This method should work equally well with any library. The most
important one being the function FRE() which is designed specifically to return
the free memory within the memory block we are discussing.
FRE() returns free memory without a garbage collection
(includes unused strings)
FRE(0) forces a garbage collection, then returns the result
(this is the important one)
FRE(0) happens to be very accurate, however we want to know how much memory is
used by our arrays and strings, not how much memory is free for new ones. The
solution is to save the value of FRE(0) right away at the start of the program
and subtract FRE(0) from that value later. We are also going to use a simple
method to note the peak amount of ram used.
In order to monitor the amount of ram used in real-time we are going to use the
EVERY command to make a simple interrupt that calculates the ram used and
displays it on the screen somewhere. We are also going to use ATEXT to display
it, this way it doesn't disrupt the VDI settings or PRINT while the program is
running.
Here's an example listing:
' $M256000 !remove if not using malloc() or set it unusually high
$I+,U+ !required for the EVERY command
'
' if using RESERVE in a compiled program (hope not), it must be here
meminit__%=FRE(0) !must be first line of code
EVERY 200 GOSUB mem_check !200=1 second, adjust tick value if needed
'
' your program would be here, for this example we do a little comparison test
it%=100 !iterations
DIM test$(it%)
'
CLS !assumes the console is open
CLR c% !calculate the size of the array
ADD c%,6 !array descriptor
ADD c%,4 !1st dimension
ADD c%,4 !back-trailer
ADD c%,4 !block size
FOR i%=0 TO it%
test$(i%)=SPACE$(2+BCLR(RANDOM(126),0)) !even arbitrary text that's not null
ADD c%,6 !string descriptor
ADD c%,LEN(test$(i%)) !string data size
ADD c%,4 !back-trailer
NEXT i%
PRINT "calculated size: ";STR$(c%,8) !the results of our own calculation
DO
~GRAF_MKSTATE(mx&,my&,mk&,sk&)
LOOP UNTIL mk&=2 !exit if mouse button #2 is pressed?
'
PRINT "difference: ";ABS(c%-memsize__%) !my comparison
' PRINT memsize__% !disable line-a stuff, used to avoid writing to the screen
' ~INP(2) !hold screen until a key is pressed
EDIT
'
PROCEDURE mem_check
memsize__%=MAX(memsize__%,meminit__%-FRE(0)) !calc peak ram used
ACLIP 1,0,0,WORK_OUT(0),WORK_OUT(1) !don't disturb the vdi
ATEXT 30*16,0,2,STR$(memsize__%,8)+" "+TIME$ !dump time to see the ticks
RETURN
You can see by the example its fairly accurate in determining the peak amount
of ram used. For some reason it's always 6 bytes off. I'm not sure the
calculation I do for the comparison is correct. Its based on some descriptor
documentation I found in a German hypertext. I don't think 6 bytes is going to
make or break the idea I'm trying to convey here.
Comment out any $m you may already have and let the program eat all available
ram for this test. If you are using MALLOC() then adjust $m to some unusually
large value, so your MALLOC()'s won't fail. Now add the lines in bold to a copy
of your source code and compile it. You might have to adjust the x/y position
of the ATEXT command for whatever resolution you are using. If for some reason
you can't display the value while the program is running, simply comment out
the Line-A commands and dump the variable "___________" when the program ends.
I suggest putting the program through its paces, hammer on it, do whatever it
takes to produce the worst possible scenario that will consume the most ram.
It's the peak value you are trying to achieve. Lowering the tick value of the
EVERY command will slow the program down, but increase the accuracy of the
real-time monitoring.
Once you have this peak value, its only a matter of deciding how much extra to
add as a safety net, then you have your final value for $m. This method is also
not perfect, but it gives you a base value to begin with as opposed to guessing
entirely. ;o)