Specifying the size of the program cache

A customer asked

1) Is it better to set it to 0 (Auto) or some "larger" number, such as -16 (1024kb). I haven't found any information in the help files as to where the tradeoff lies between these, but presumably the default for MTDLLs of 128kb is deliberately small to prevent each thread requiring too much RAM.

2) The second thing is that when I query the current PROGCACHE setting (using SYS(3065) for the DLL without any CONFIG.FPW, it sometimes returns -2 (the stated default) and sometimes 0. I suspect this could be why the problem seems to be intermittent, with a page refresh often causing the second request to work. Could this be a VFP bug or something to do with the configuration of the webserver and how it is calling the COM object?

The ProgCache setting is used to set how much memory VFP will set aside for the program cache. When you execute some code, it gets loaded and stored into this cache. When a program gets loaded, the binary compiled file gets loaded and fixups are applied. Such fixups are things like names in the compiled code are mapped to names in the current name table. For VFP versions prior to 9, the cache was set to about10 megs, and the user could not change it. For multiple threads, each thread would create its own cache, which means 20 threads would use 200 megs of memory. Actually, the memory isn’t really used until necessary: it’s VirtualAlloc’ed with the MEM_RESERVE parameter, which reserves 200 megs of virtual address space out of the available 2^32 = 4 gigs.

For VFP9, I added the PROGCACHE setting that user’s can set in config.fpw. Setting it to 0 means that no cache will be used: programs will be loaded into dynamically allocated memory, rather than a prereserved cache.

To query the ProgCache setting, use SYS(3065)

Keep in mind that “without any CONFIG.FPW” should be confirmed via querying both internal and external CONFIG.FPW files (sys(2019))

Try running the code below, varying the progcache setting and the size of the generated program (n) to see the effects. It generates a VFP COM server with a particular ProgCache setting, and then tries to run a very large program. Try causing a failure with various ProgCache settings.

See also: Collecting garbage at the wrong time

Using Very large programs

For more about the program cache, see Windows Security and how it affects running generated code

For more about memory usage, see Inspect your memory image and see fragmentation

CLEAR ALL

CLEAR

#define cProgName "testPCCache"

Cleanup()

TEXT TO cStr NOSHOW textmerge

EXTERNAL FILE config.fpw && comment this line in/out

DEFINE CLASS c1 as session olepublic

          func MyEval(cExpr as string, p2 as Variant, p3 as Variant, p4 as Variant, p5 as Variant) as variant helpstring "this is the Eval help"

                   RETURN &cExpr

          func MyDoCmd(cCmd as stRing, p2 as Variant, p3 as Variant, p4 as Variant, p5 as Variant) helpstring "this is the docmd help"

                   &cCmd

ENDDEFINE

ENDTEXT

STRTOFILE(cStr,cProgName+".prg")

TEXT TO cStr NOSHOW textmerge

*progcache=0 && uncomment this line to see the results

ENDTEXT

STRTOFILE(cStr,"config.fpw")

BUILD PROJECT (cProgName) FROM (cProgName)

BUILD MTDLL (cProgName) FROM (cProgName)

ox=CREATEOBJECT(cProgName+".c1")

?ox.MyEval("_vfp.ServerName")

?"Cfg=",ox.MyEval("sys(2019,2)")

?"Sys(3065,0)",ox.MyEval("sys(3065,0)")

?"Sys(3065,1)",ox.MyEval("sys(3065,1)")

n=578 && 578 passes, 579 fails w/ -2

SET TEXTMERGE TO tt2.prg on noshow

          \y='a' && this is used in a CASE statement *way* down there

          \Do Case

          \Case Y = 'a'

          FOR i = 1 TO n

                   \ x="<<REPLICATE("a",100)>>"

          ENDFOR

\ y="b"

\Case .T.

          FOR i = 1 TO n

                   \ x="<<REPLICATE("a",100)>>"

          ENDFOR

         

\ y = "a"

\ENDCASE

\set compatible on

\IF y="b" then

\ cret= "DOCASE:PASS "+TRANSFORM(FSIZE("tt2.prg"))

\ELSE

\ cret= "DOCASE:FAIL "+TRANSFORM(FSIZE("tt2.prg"))

\endif

\set compatible off

\return cret

SET TEXTMERGE to

COMPILE tt2

try

          ?ox.myeval("tt2()")

CATCH TO oex

          ?oex.message

ENDTRY

ox=0

Cleanup()

PROCEDURE Cleanup

          IF FILE(cProgName+".dll") && cleanup

                   DECLARE integer DllUnregisterServer IN (cProgName)

                   DllUnregisterServer()

                   CLEAR DLLS

          ENDIF

RETURN