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