Heartbeat: Garbage collection in VFP and .NET are similar

VFP stores user defined names such as variable, field, property, class and procedure names in a table.

When the name table runs out of space, the GC begins. All the entries are marked as unused. Then all the various name table clients are sent a message to flag their names as used. These clients include the object manager, the database engine, the variable manager, and the compiled code manager. The remaining names that are marked as unused are purged from the table.

This algorithm is very much like the .NET garbage collector. There is no reference counting of names, and unused names cannot be referenced. However, since there are no references or pointers, the GC is much simpler in VFP.

You can see the effect of the garbage collection by timing how long it takes to run a loop that creates and discards batches of unique names.

Run the code below, and you’ll get a heartbeat EKG on your screen. You might need to adjust the scaling factors depending on the performance of your machine. The x axis is time and the y axis is the amount of time it takes per batch

The resulting graph shows how long it takes to add bunches of 10,000 names to the table. The time varies periodically, repeating about every 6 batches. If each bunch took exactly the same amount of time, the graph would be flat (and the heart would not be beating).

As the name table fills, it takes longer to add a name. When the table is full, a garbage collection is done, which clears out much of the table.

Repetition occurs about every 6 batches because the name table size is around 65000 names, and not every name is garbage when the collection occurs.

Let’s become cardiologists and examine the shape of the heartbeat. The lowest part of the EKG is actually the batch that took the longest time (highest numerical value since the origin is the top left of the screen). After the name table has been garbage collected, it’s mostly empty. As it gets filled, the time per batch increases.

CLEAR

x0=0

y0=0

nPrior=0

SET DECIMALS TO 3

CREATE CURSOR results (x i,nsecs b,delta b)

BROWSE LAST nowait

FOR i = 1 TO 200

      nsec=SECONDS() && start time

      num=5000

      usenames(i,num)

      ntime=SECONDS()-nsec && end time

      nval=ntime-nPrior && delta (# of names/sec)

* ?i,ntime,ntime,nval

      y1=ntime*3000 &&+_screen.Height/2 &&scale to fit in middle of screen

      x1=i*4 && scale x to show better

      INSERT INTO results VALUES (i,ntime,nval)

      _screen.Line(x0,y0,x1,y1) && draw a line to connect dots

      x0=x1 && advance prior point

      y0=y1

      nPrior=ntime && and prior value

ENDFOR

PROCEDURE usenames(base,n)

      LOCAL i

      FOR i = 1 TO n

            cvar="x"+PADL(base,3,"0")+TRANSFORM(i) && create a unique var name like "x0011"

            STORE i TO (cvar) && create the var

      ENDFOR

RETURN && vars get released when they go out of scope

 

48919