Revised January 20th, 2006
I like to see example code - and thankfully there's no shortage of it on the various FoxPro websites. I do wish that some of these examples were organized in such a way that I could use them immediately. As an example, this code from Craig Boyd demonstrates using the Windows QueryPerformanceCounter function as a better way to time code snippets. It's a great example and a valid and useful technique. Don't worry, despite the name, it's not another rumour about the possible demise of VFP. Execution time for VFP.
I can only re-use that code if I copy-and-paste it into various PRGs. What I'd like to do is drop that code into a single PRG and never touch it again - but still be able to re-use it.
If the code was packaged as a class or PRG, I could just drop it into some directory in my VFP search path and instantiate or call it.
I refactored Craig's sample to demonstrate - no offence Craig! First off, here's the calling code.
SET PROCEDURE TO StopWatch.PRG ADDITIVE
LOCAL m.loStopWatch, m.lcStartBuffer, m.lcEndBuffer, m.lcA, m.lnI
m.loStopWatch = CREATEOBJECT("tmrStopWatch")
#DEFINE kIterations 5000
m.lcStartBuffer = m.loStopWatch.Start()
FOR m.lnI = 1 TO kIterations
m.lcA=TRANSFORM(m.lnI)
ENDFOR m.lnI
m.lcEndBuffer = m.loStopWatch.Stop()
? "Elapsed time in seconds TRANSFORM(): " ;
+ TRANSFORM(m.loStopWatch.Elapsed(m.lcStartBuffer,m.lcEndBuffer))
m.lcStartBuffer = m.loStopWatch.Start()
FOR m.lnI = 1 TO kIterations
m.lcA=ALLTRIM(STR(m.lnI))
ENDFOR m.lnI
m.lcEndBuffer = m.loStopWatch.Stop()
? "Elapsed time in seconds ALLTRIM(STR()): " ;
+ TRANSFORM(m.loStopWatch.Elapsed(m.lcStartBuffer,m.lcEndBuffer))
As you can see that's almost as simple as using SECONDS(). StopWatch.PRG contains the tmrStopWatch class. Here's Craig's code repackaged as a class.
*
* StopWatch.PRG
* This .PRG contains the VMP base class definition
* for the StopWatch class which exposes the Windows
* QueryPerformanceCounter. This lets you check the
* elapsed time for anything you need to test with
* greater precision than SECONDS().
*
* Thanks to Craig Boyd for the original code.
* Thanks to Fabio Lunardon for suggested
* refinements.
* Thanks to Gene Pasquini for finding an incorrect
* call to DWord2Num - which should have been
* QWord2Num.
*
* Author: Mike Yearwood
* FoxRidge Software Inc.
* http://www.foxridgesoftware.com
*
**********************************************************
**********************************************************
#DEFINE kBuffer REPLICATE(CHR(0),8)
DEFINE CLASS
tmrStopWatch AS TIMER
PROTECTED inTicksPerSecond
PROTECTED inOverhead
PROTECTED icQueryPerformanceCounterName
PROTECTED icQueryPerformanceFrequencyName
inOverhead = 0
inTicksPerSecond = 0
HIDDEN PROCEDURE INIT
THIS.LoadDLLS()
THIS.SetFrequency()
THIS.SetOverhead()
ENDPROC
HIDDEN PROCEDURE Destroy
THIS.ReleaseDLLS()
ENDPROC
HIDDEN PROCEDURE LoadDLLS
LOCAL m.lcRandom
STORE SYS(2015) TO m.lcRandom
STORE "QPC" + m.lcRandom TO THIS.icQueryPerformanceCounterName
DECLARE INTEGER QueryPerformanceCounter ;
IN kernel32 ;
AS (THIS.icQueryPerformanceCounterName) ;
STRING @lpFrequency
STORE "QPF" + m.lcRandom TO THIS.icQueryPerformanceFrequencyName
DECLARE INTEGER QueryPerformanceFrequency ;
IN kernel32 ;
AS (THIS.icQueryPerformanceFrequencyName) ;
STRING @lpFrequency
ENDPROC
HIDDEN PROCEDURE ReleaseDLLS
LOCAL m.lcDLLS
m.lcDLLS = THIS.icQueryPerformanceCounterName ;
+ "," ;
+ THIS.icQueryPerformanceFrequencyName
CLEAR DLLS &lcDLLs.
ENDPROC
HIDDEN PROCEDURE SetFrequency
LOCAL m.lcCurrentBuffer
STORE kBuffer TO m.lcCurrentBuffer
EVALUATE(m.THIS.icQueryPerformanceFrequencyName+"(@m.lcCurrentBuffer)")
THIS.inTicksPerSecond = QWord2Num(m.lcCurrentBuffer)
ENDPROC
HIDDEN PROCEDURE SetOverhead
LOCAL m.lnOverhead, m.lnK
STORE 0 TO m.lnOverhead
FOR m.lnK = 0 TO 100
STORE m.lnOverhead + THIS.Elapsed(THIS.START(),THIS.Stop()) TO m.lnOverhead
NEXT
THIS.inOverhead = m.lnOverhead / m.lnK
ENDPROC
HIDDEN PROCEDURE CurrentTime
LOCAL m.lcCurrentBuffer
STORE kBuffer TO m.lcCurrentBuffer
EVALUATE(THIS.icQueryPerformanceCounterName+"(@m.lcCurrentBuffer)")
*The state of the buffer must be
*preserved so pass it back. This permits
*multiple callers to use this same
*StopWatch instance simultaneously.
RETURN m.lcCurrentBuffer
ENDPROC
PROCEDURE Start
RETURN THIS.CurrentTime()
ENDPROC
PROCEDURE Stop
RETURN THIS.CurrentTime()
ENDPROC
PROCEDURE Elapsed
LPARAMETERS m.tcStartBuffer, m.tcEndBuffer
RETURN (QWord2Num(m.tcEndBuffer) ;
- QWord2Num(m.tcStartBuffer)) ;
/ THIS.inTicksPerSecond ;
- THIS.inOverhead
ENDPROC
ENDDEFINE
There's a good reason to remember the initial value of m.lcStartBuffer. Suppose you have various processes running. Further, suppose they're all managed by a single master process. Because of the design of this class, each piece can check the elapsed time without interfering with the elapsed time of the other processes. That would not be possible if the class held the start time as a property. That start time property would be overwritten as each process started.
There are two other little .PRGs we need. Instead of putting BUF2NUM right in the cusStopWatch class, I strongly advise you make a separate BUF2NUM.PRG file. Just put the code from Craig's BUF2NUM function into that .PRG and VFP will run it like a function when called as above. That gives you the additional reuse of that .PRG and reduces the existence of multiple copies of that code in your projects. I changed the name of it from BUF2NUM to DWORD2NUM. The function is converting a DWORD to a number.
The QueryPerformanceCounter and QueryPerformanceFrequency functions both use 2 buffers, a pair of DWORDs. That's called a QWord (quad word). That means we should have a QWord2Num program.
*
* QWord2Num.PRG
* Converts an 8 byte/64 bit character representation
* of a binary value to a numeric value
*
* Copyright (c) 2005-2008 Fox Ridge Software
* All Rights Reserved
* 120 Parsell Square
* Scarborough, ON M1B 2A6
* 416-282-3942
* http://www.foxridgesoftware.com
* Author: Mike Yearwood
*
* Usage:
* LOCAL lcBuffer
* lcBuffer = CHR(65)+CHR(65)+CHR(65)+CHR(65)+CHR(65)+CHR(65)+CHR(65)+CHR(65)
* ?QWord2Num(m.lcBuffer)
*
* Parameters
* tcBuffer (R) Buffer string holding the binary data.
*
LPARAMETERS m.tcBuffer
IF VARTYPE(m.tcBuffer) # "C" ;
OR LEN(m.tcBuffer) < 8
ERROR 11
ENDIF
LOCAL m.lnValue
m.lnValue = DWord2Num(SUBSTR(m.tcBuffer, 1,4)) ;
+ (DWord2Num(SUBSTR(m.tcBuffer, 5,4)) * 0x100000000)
RETURN m.lnValue
You'll notice this QWord2Num function calls the DWord2Num function:
*
* DWORD2NUM.PRG
* Converts a 4 byte character representation
* of a binary value to a numeric value
*
* Copyright (c) 2005-2008 Fox Ridge Software
* All Rights Reserved
* 120 Parsell Square
* Scarborough, ON M1B 2A6
* 416-282-3942
* http://www.foxridgesoftware.com
* Author: Mike Yearwood
*
* Usage:
* LOCAL lcBuffer
* lcBuffer = CHR(65)+CHR(65)+CHR(65)+CHR(65)
* ?DWORD2NUM(m.lcBuffer)
*
* Parameters
* tcBuffer (R) Buffer string holding the binary data.
*
LPARAMETERS m.tcBuffer
IF VARTYPE(m.tcBuffer) # "C" ;
OR LEN(m.tcBuffer) < 4
ERROR 11
ENDIF
LOCAL m.lnValue
#IF LEFT(VERSION(4),2)="09"
m.lnValue = CTOBIN(m.tcBuffer,"R") + 0x80000000
#ELSE
m.lnValue = ASC(SUBSTR(m.tcBuffer, 1,1)) ;
+ ASC(SUBSTR(m.tcBuffer, 2,1)) * 0x100 ;
+ ASC(SUBSTR(m.tcBuffer, 3,1)) * 0x10000 ;
+ ASC(SUBSTR(m.tcBuffer, 4,1)) * 0x1000000
#ENDIF
RETURN m.lnValue
I'll make every effort to provide re-usable examples here.
December 29th, 2005
I just read about the StopWatch class in .Net 2.0. Seems it doesn't permit multiple clients to share one instance. http://www.codinghorror.com/blog/archives/000460.html