[comp.lang.c] memory allocation/deallocation for tree

fangchin@portia.Stanford.EDU (Chin Fang) (01/11/91)

First of all I would like to give my thanks to all people who
have kindly responded to me (so far more than ten, still coming). 

From the rate of response, it seems that my question was not a too
boring one.  Most people even sent me pieces of codes to make their
ideas clear.  

I am not ready to make a detail summary so far. So I briefly mention
the most common suggestion to my problem, assuming I still use 
balanced red-black tree for data caching.

(1) use malloc as less often as possible, it is expansive. Always allocate in  
    large chunks. enlarge space allocated using realloc(3C)(3X) if
    necessary.
    (an implication of this is: blindly using the utilities in the 
     Appendix D of Numerical Receipt to get so called completely
     dynamic memory management MAY cost one A LOT! depending the # of calls.
     Rewrite them so that the following. See (2) (3))

(2) a private management will speed up process and reduce running costs
    significantly most of the time.  Using a linked list is the most
    common suggestion.

    One idea, putting an extra field in the node decl in addition to
    the left and right pointers.  Allocate a bunch of them, link them.
    When start building tree, each time get a node from this linked 
    list instead call malloc() directly.

    Two benefits:  cheaper. less malloc() calls.
                   memory-wise more efficient, since malloc() spends 
                   less memory for housekeeping. (=> faster too)

(3) If nodes on the linked list run out, just allocate another chunck,
    break them up into nodes, and join them to the existing linked
    list again. (see also (1))

(4) If a tree node is deleted, put it back to the linked list for 
    future use.

    (This part is hard to me, because tree node deletion for Red-black
     balanced 2-3-4 tree is difficult to write simple and well.
     Particularly I had to deal with key cascades, ie. primary key,
     2ndary key, 3rd key, not just one int key that one typically reads in
     data structures text books.

     I would appreciate any sample that you consider good and am 
     willing to let me take a look. Please email.)

(5) If the tree needs to be take apart completely.  Just scan the 
    linked list, free it all. The tree vaporizes too since it's 
    nodes are all free-ed. (For black-red tree, if dummy nodes are
    used, there are two remaining, head and another ground node,
    but these two are easy to deal with).

(6) If data caching becomes involved to implement, building dynamical
    data structures may not gain due to the complexity of codes and
    the time they require to execute.  A simple static (and/or global)
    storage may do the job cheap and efficiently too. (Yeah, you are
    right.....)

Many people asked me where I got my impression that I had to deallocate
in EXACTLY the reverse order.  I would like to answer this here too.

I do programming most of the time on my i386 box running AT&T System V
R.3.2. for which I believe malloc(3C) uses circular list for
maintaining free list.
 
I don't have access the src. This is the impression that I got from
reading the man page in Prentice-Hall UNIX System V/386 Programmer's
Reference Manual.  I did the following testing too:

allocate a chunck of blocks, save their addresses.
deallocate them in different order and after done, reallocate the same
number of blocks, and write out their addresses, compare with the
saved addresses to see which order of allocation/deallocation would
give me the same lists.  It turned out only precisely the reverse
order would give me such on my machine (Thanks Steve).

Also see ps .2 below.

Anyone who has access to an i386 box running ATT UNIX should get the
same results as mine. I think Interactive, Micropot, UHC, ESIX, and SCO
are all from the same place basically other than some "enhancement?"
added later on by different vendors. But as long as the C Software
Developement Set is pcc based, libs should be the same and therefore 
malloc()/free() would behave the same as mine. (SCO and Interactive
may provide different compilers, eg MS C for SCO).  

Since many people asked me such.  So in the past few days, I tried Sun
C libs as well.  The outcome?  No any order would produce the same 
address lists.  All bats are off!  Can someone explain this?
I haven't try DEC's C libs on VAX/MIPS and IBM C libs on RS/6000 yet.
I will however. (Note, what I used was not the new ANSI C libs from 
SUN, it was the old implementation that comes with SUN OS 4.01?).

So whether the order of allocation/deallocation matters is still not 
too clear to me.  I guess only computational experiments would sort
out a claim then. I will write some test codes and try to monitor
their run time memory usuage to see if any claim is valid in general
accross some popular C implementations.

Regards and Thanks again to all people who responded.

Chin Fang
Mechanical Engineering Department
Stanford University
fangchin@portia.stanford.edu

ps. 1. I normally always return people's mails.  But due to my school's 
       mail server problems, I have been unable to reply to a few people.
       I apologize. I saved all your input and I will try later when
       the mail server becomes more stable.

    2. The following simple codes were used for testing addresses:

*** Code Used (originally from Mr. Steve Clamage, steve@taumet.com, Thanks)***

#include <stdio.h>

#define DATASIZE 123     /* each allocation block */
#define RAYSIZE 10       /* how many blocks */

int main(void)
{
    void *save[RAYSIZE];
    int i;
 
    for ( i = 0; i < RAYSIZE; ++i)
        save[i] = (void *)malloc(DATASIZE);   /* recording each block's add */
    for (i = RAYSIZE-1; i >= 0; i -= 2)      
        free(save[i]);                        /* free odd #-ed blocks */
    for ( i = 0; i < RAYSIZE; i +=2)
        free(save[i]);                        /* free even #-ed blocks */
    for ( i = 0; i < RAYSIZE; ++i)
        printf("old=%u, new=%u\n", save[i], (void *)malloc(DATASIZE));
        /* now compare */
    return 0;
}

******** The following is for reverse order release  ***************

#include <stdio.h>

#define DATASIZE 123     /* each allocation block */
#define RAYSIZE 10       /* how many blocks */

int main(void)
{
    void *save[RAYSIZE];
    int i;
 
    for ( i = 0; i < RAYSIZE; ++i)
        save[i] = (void *)malloc(DATASIZE);   /* recording each block's add */
    for (i = RAYSIZE-1; i >= 0; i --)      
        free(save[i]);                        /* free in reverse order */
    for ( i = 0; i < RAYSIZE; ++i)
        printf("old=%u, new=%u\n", save[i], (void *)malloc(DATASIZE));
        /* now compare */
    return 0;
}
*************** The above is for reverse order release **************

kpv@ulysses.att.com (Phong Vo[drew]) (01/11/91)

In article <1991Jan10.203552.9752@portia.Stanford.EDU>, fangchin@portia.Stanford.EDU (Chin Fang) writes:
> 
> (1) use malloc as less often as possible, it is expansive. Always allocate in  
>     large chunks. enlarge space allocated using realloc(3C)(3X) if
>     necessary.
For every malloc implementation that I know of, every malloc request is
rounded up to a multiple of sizeof(double) and every allocated block
entails a hidden overhead of at least one word. So, yes, if you know
that you are allocating many objects of the same size, get large chunks
and use them as you need.
> 
>     One idea, putting an extra field in the node decl in addition to
>     the left and right pointers.  Allocate a bunch of them, link them.
Just a trivial observation but when a node is not used, either the left
or right pointer can be used for the link in the free list. There is no
need for another field.
> 
> Since many people asked me such.  So in the past few days, I tried Sun
> C libs as well.  The outcome?  No any order would produce the same 
> address lists.  All bats are off!  Can someone explain this?
Sun malloc coalesces adjacent free blocks immediately. So any order of
free will ultimately result in one big free block. The older SysV malloc does
not merge adjacent free blocks until it is necessary to do so. In addition,
after a block is free, the search for the next malloc request is started
at that address. Therefore, only a reverse order free sequence will put the
search for the next round of malloc at the right place (as you want it).
> 
> So whether the order of allocation/deallocation matters is still not 
> too clear to me.  I guess only computational experiments would sort
As far as efficiency is concerned, it shouldn't make any difference,
especially if you are using a more modern malloc like Sun's or SysVr4's.
For correctness, when deleting tree and list structures, it may be vital
that some form of the reverse order is used. For example, the following
piece of code to free a list is fatally wrong but it'll work with some
mallocs such as SysVr3's or BSD's:
	for(e = list; e; e = e->next)
		free(e);