[comp.sys.mac.programmer] Large Programs and Memory Management Questions

slang@bmerh563.bnr.ca (Steven Langlois) (04/24/91)

I have some questions about memory management with large applications. Unfortunately, most
sample code from any source is not nearly large enough to encounter memory management issues.

The application I have been working on for some time now is approaching 375K. I have set the 
SIZE resource to specify an application memory size of 512K. Far too much of the available
512K is used up by code segments. Now I know about UnloadSeg and intend to use it but 
this is where my questions come into play.                                            

What is the best method for unloading segments?

There is the "unload all your segments every time through your event loop" method but is this an
effective method or are there other better methods? 

From what I have read, it is typically a good idea to unload your initialization 
segment before entering your main event loop. You will then not have to unload the this
segment again because you know that you will never call the initialization code again.
After doing this should you compact the heap or wait until a call to the memory 
manager does it for you?    

Should your application periodically compact the heap to keep it from getting fragmented? If so,
when is a good time, maybe after the user executes a menu command?

For those knowledgeable in THINK Pascal, is it a good item to load and lock the runtime 
environment or should you allow it to be unloaded like any other segment?

What would happen if there was not enough memory to load a CODE segment? Would the program
crash or just not call the routine in the segment? And more importantly, how do you prevent
this from happening, especially when calling the routine may cause a chain of segments
to be loaded?

Well, I guess that will do for now. Any help would be greatly appreciated.


Steven Langlois
ISDN Basic Rate Access
BNR, a subsidiary of Northern Telecom

Bitnet: slang@bnr.ca
Bitnet from AppleLink:  slang@bnr.ca@DASNET#                                    

dorner@pequod.cso.uiuc.edu (Steve Dorner) (04/25/91)

>There is the "unload all your segments every time through your event loop"
>effective method or are there other better methods?

If you know your code in and out, you could unload segments on the fly.  Just
be darn careful you don't unload a segment that holds anything on the
current call stack.  You're PROBABLY better off not doing this, unless
you have a very special program, since it will require you to do a lot
of careful bookkeeping on what routines call what and what segments
they're in, and will probably not gain you much anyway.
 
>Should your application periodically compact the heap to keep it from
>getting fragmented?

No.  Won't do you any good.  Just make sure you don't have relocatable
blocks lying around and you'll be fine.

>What would happen if there was not enough memory to load a CODE segment?

La Bombe.

>And more importantly, how do you prevent
>this from happening

There are a couple of ways.  You can reserve a goodly chunk of memory, and
release it in your GrowZone procedure; this will cover your posterior often.
The other way is to have intimate knowledge of inter-segment references,
and always preflight such calls by checking if there is enough memory
to load the needed segment.  I think MacApp does this.  I doubt it's practical
for the average developer, unless you come up with an elaborate segmentation
system.

  if (EnoughMemoryForCodeSegment(32)) CallSomeFunction(withSomeArg);
  else RefusePolitely();

would get real old if it weren't being done automagically by your development
environment.

I'd suggest you get a copy of "How to Write Macintosh Software" by Scott
Knaster.  It's a big help with issues like this.
--
Steve Dorner, U of Illinois Computing Services Office
Internet: s-dorner@uiuc.edu  UUCP: uunet!uiucuxc!uiuc.edu!s-dorner

dorner@pequod.cso.uiuc.edu (Steve Dorner) (04/25/91)

I mumbled:

>No.  Won't do you any good.  Just make sure you don't have relocatable
>blocks lying around and you'll be fine.

I meant "non-relocatable", of course.
--
Steve Dorner, U of Illinois Computing Services Office
Internet: s-dorner@uiuc.edu  UUCP: uunet!uiucuxc!uiuc.edu!s-dorner

pratt@boulder.Colorado.EDU (Jonathan Pratt) (04/25/91)

In article <1991Apr24.170354.20749@ux1.cso.uiuc.edu> dorner@pequod.cso.uiuc.edu (Steve Dorner) writes:
>
[Stuff about making sure it's safe to call another segment]

>  if (EnoughMemoryForCodeSegment(32)) CallSomeFunction(withSomeArg);
>  else RefusePolitely();
>
I thought I'd share my version of EnoughMemoryForCodeSegment.  I don't
find it too inconvenient to use, and it's definitely preferable to giving
the end user a system error.  My typical usage is

if (LoadCheck((short *) AFunction) {
	AFunction(...);
	UnloadSeg(AFunction);
}

All of my base code (libraries, utilities, etc.) is preloaded and locked,
so the LoadCheck bit is used principally for isolated functions, although
for safety I use it before dispatching to routines associated with menus,
windows, etc. (which are known only by ProcPtr variables).  No flames
please about the in-line assembly.  You have to have it to call LoadSeg
(a trap THINK C doesn't seem to recognize).  I've been assueming that
in any case where LoadCheck succeeds, the call to the function won't
scramble memory provided the function itself doesn't.  Can anyone
contradict this?

short LoadCheck(theproc)
register short *theproc;
{
/* This next line makes sure there is a LoadSeg call in the jump table      *
   so that it's safe to extract the segment number to user with GetResource.*/
	if (*theproc == 0x3f3c && *(theproc+2)== 0xa9f0)
		if (!GetResource('CODE',*(theproc+1)) ) {
			GiveTheError(-108);
			return false;
		}
		else
			asm {
				Move.W 2(theproc),-(A7)
				Bra.S @in
				Bra.S @cont
				NOP
			@in
				DC.W 0xa9f0		/* _LoadSeg */
			@cont
			}
	return true;
}

An obvious improvement is to use ResError() instead of -108 in
GiveTheError.  That way you would accurately tell the user if it
the failure was caused by a disk error rather than insufficient
memory.

/* Jonathan Pratt            Internet: pratt@boulder.colorado.edu  *
 * UCHSC, Box A034           uucp: ..!ncar!boulder!pratt           *
 * 4200 E. Ninth Ave.                                              * 
 * Denver, CO 80262          Phone: (303) 270-7801                 */

Lawson.English@p88.f15.n300.z1.fidonet.org (Lawson English) (05/01/91)

Steven Langlois writes in a message to All

SL> For those knowledgeable in THINK Pascal, is it a good item to 
SL> load and lock the runtime environment or should you allow it 
SL> to be unloaded like any other segment?

I can't answer any of the other questions, but, if you check, the runtime environment
of Think Pascal should be specified as un-unloadable by default if you use one
of the example programs to build on (at least mine is).


Lawson
 

--  
Uucp: ...{gatech,ames,rutgers}!ncar!asuvax!stjhmc!300!15.88!Lawson.English
Internet: Lawson.English@p88.f15.n300.z1.fidonet.org

lsr@Apple.COM (Larry Rosenstein) (05/03/91)

In article <1991Apr24.122618.13791@bmers95.bnr.ca> slang@bmerh563.bnr.ca (Steven Langlois) writes:

>What is the best method for unloading segments?

In MacApp we unload non-resident segments after each event.  (The routine to
do this can be called at any time, if you're careful.)  Resident segment is
a MacApp concept.  In the simplest case, the app includes a resource listing
the names of the resident segments.  You can also mark segments as resident
programmatically.

>There is the "unload all your segments every time through your event loop"
>method but is this an effective method or are there other better methods?

Some people have proposed trying to figure out if a segment is being used by
tracing down the call stack, but I'm not sure if this is any better.  (I
think the idea was to do this when you needed extra memory.)  Unloading a
segment doesn't purge it from memory; it simply marks the jump table entries
as unloaded, and allows the segment to float in the heap.  If you use it
again before it gets purged, then it doesn't have to be read from disk.

>From what I have read, it is typically a good idea to unload your
>initialization segment before entering your main event loop. You will then

You should be careful that unloading this segment doesn't create a hole in
the heap because other segments were loaded after it and these weren't also
unloaded.  

For example, you know that the main segment is loaded first and
never gets unloaded.  It can make a call to the initialization code, which
will load various segments.  Before starting the main event loop, you can
unloaded all segments except the main segment, in order to start with a
clean slate.  

It help to check the state of your heap at various times to make sure that
there are no free block surrounded by locked code segments.

>compact the heap or wait until a call to the memory manager does it for
>you?

It should never be necessary to call explicitly compact the heap, if there
are no non-relocatable or locked blocks in the middle of the heap.  You also
have to be careful about a very large relocatable block that's bigger than
any free space, because it is essentially locked and can't be moved.

>What would happen if there was not enough memory to load a CODE segment?

You get a system error.  The same is true of failing to load other resources
like defprocs, packages, ...

>more importantly, how do you prevent this from happening, especially when

As someone said you need to keep a reserve handle around that you can free
if necessary.  The Memory Manager allows you to install a grow zone proc
that will be called if it needs more memory.  You can release the handle in
your grow zone.  MacApp does essentially this except that we try to be
clever about the size of this handle so that it doesn't take up memory
needlessly.

The concept is that your application needs X bytes of space for the maximum
amount of code, defprocs, ... you use at any point in time.  To prevent a
crash, you have to make sure that you have this amount of memory available.
Segments that are already in memory count towards satisfying this
requirement.  So the reserve handle size can be reduced by the total sizes
of in-memory code segments.

Another subtle point is that you only need to guarantee this reserve memory
before you make a non-segment request.  (In MacApp we call this a permanent
request, because the memory is permanently allocated in the document, and
can't be freed unless the user deletes something.)

MacApp maintains a flag that indicates whether the program is in permanent
or temporary request mode.  When the flag is changed from temporary to
permanent we make sure that the reserve handle is allocated properly.  The
grow zone proc also uses the flag to decide if the reserve should be freed.

This scheme works very well as long as you tell MacApp the maximum amount of
loaded code, defprocs, ... you need.  The MacApp debugger has tools that
help to figure this out, and the sizes are specified in resources.  And you
still need to check whether permanent allocations succeed, and gracefully
back out if not.

-- 
Larry Rosenstein, Apple Computer, Inc.

lsr@apple.com
(or AppleLink: Rosenstein1)

<CHARLESW@QUCDN.QueensU.CA> (05/14/91)

  There is also a good article on MacApp's memory management in the Fall/89
"Dr. Dobb's Macintosh Journal" by Curt Bianchi.  It applies to MacApp 2.0b9.

.../Dave