Jump to content

Sky Slate Blueberry Blackcurrant Watermelon Strawberry Orange Banana Apple Emerald Chocolate
Photo

QPX() & Delay() :: Based on QueryPerformanceCounter()


  • Please log in to reply
14 replies to this topic
SKAN
  • Administrators
  • 9115 posts
  • Last active:
  • Joined: 26 Dec 2005
QPX()
 
QPX( N=0 ) { ; Wrapper for QueryPerformanceCounter()by SKAN | CD: 06/Dec/2009
	Static F,A,Q,P,X ; www.autohotkey.com/forum/viewtopic.php?t=52083 | LM: 10/Dec/2009
	If	( N && !P )
		Return	DllCall("QueryPerformanceFrequency",Int64P,F) + (X:=A:=0) + DllCall("QueryPerformanceCounter",Int64P,P)
	DllCall("QueryPerformanceCounter",Int64P,Q), A:=A+Q-P, P:=Q, X:=X+1
	Return	( N && X=N ) ? (X:=X-1)<<64 : ( N=0 && (R:=A/X/F) ) ? ( R + (A:=P:=X:=0) ) : 1
}
 
Returns value will be in Seconds like 1.234567,
Where, Red is Seconds, Green is Milliseconds and Blue is Thousandth of Millisecond

Example:
 
SetBatchLines -1

;;** Basic Usage **
QPX( True ) ; Initialise Counter
Sleep 1000
Ti := QPX( False ) ; Retrieve Time consumed ( & reset internal vars )

MsgBox, 0, Sleep 1000, %Ti% seconds

;;** Extended Usage **
While QPX( 1000 ) ; Loops 1000 times and keeps internal track of the total time
Tooltip %A_Index%
Ti := QPX() ; Retrieve Avg time consumed per iteration ( & reset internal vars )

MsgBox, 0, Avg Time Taken for ToolTip, %Ti% Seconds / Iteration
 
Delay()
 
MsgBox, % Delay( 0.008 ) ; Delay for 8ms

Delay( D=0.001 ) { ; High Resolution Delay ( High CPU Usage ) by SKAN | CD: 13/Jun/2009
Static F ; www.autohotkey.com/forum/viewtopic.php?t=52083 | LM: 13/Jun/2009
Critical
F ? F : DllCall( "QueryPerformanceFrequency", Int64P,F )
DllCall( "QueryPerformanceCounter", Int64P,pTick ), cTick := pTick
While( ( (Tick:=(pTick-cTick)/F)) <D ) {
DllCall( "QueryPerformanceCounter", Int64P,pTick )
Sleep -1
}
Return Round( Tick,3 )
}


Mystiq
  • Members
  • 83 posts
  • Last active: Nov 06 2011 07:07 PM
  • Joined: 08 Jan 2007
Thanks for this!

I tried to play around with the PerfCounter myself but got so inconsistent results that i decided to use your function instead.

Below function is meant for testing function performance and it supports up to 8 parameters which is easily extendable.

; QPXF(<passes>, <function name>, <parameter 1>, ..., <parameter 8>)
QPXF(_P, _F, _1="", _2="", _3="", _4="", _5="", _6="", _7="", _8=""){
	If not (IsFunc(_F) || _P > 0)
		Return
	Loop, %_P%
	{
		If (_1 = "")
			QPX(True), %_F%()
		else If (_8 != "")
			QPX(True), %_F%(_1,_2,_3,_4,_5,_6,_7,_8)
		else If (_7 != "")
			QPX(True), %_F%(_1,_2,_3,_4,_5,_6,_7)
		else If (_6 != "")
			QPX(True), %_F%(_1,_2,_3,_4,_5,_6)
		else If (_5 != "")
			QPX(True), %_F%(_1,_2,_3,_4,_5)
		else If (_4 != "")
			QPX(True), %_F%(_1,_2,_3,_4)
		else If (_3 != "")
			QPX(True), %_F%(_1,_2,_3)
		else If (_2 != "")
			QPX(True), %_F%(_1,_2)
		else If (_1 != "")
			QPX(True), %_F%(_1)
		T += QPX(False)
	}
Return T / _P
}

Example:

;-- run "Delay(0.5)" once
MsgBox % QPXF(1, "Delay", 0.5)

;-- same as:
QPX(true)
Delay(0.5)
MsgBox % QPX()

;-- run "Delay(0.5)" 5 times and return average
MsgBox % QPXF(5, "Delay", 0.5)

Comments are welcome. :)

Relayer
  • Members
  • 122 posts
  • Last active: Jun 22 2015 09:21 PM
  • Joined: 24 Nov 2008
hummmmm...

When I try to call Skan's Delay function I get an error from AHK saying functions cannot contain functions. I'm staring at it and it's not making sense. It is complaining about the line with the while statement.

#NoEnv
Delay(5)
msgbox, What the?
ExitApp

Delay( D=0.001 ) {  ; High Resolution Delay ( High CPU Usage )  by SKAN  | CD: 13/Jun/2009
 Static F           ; www.autohotkey.com/forum/viewtopic.php?t=52083     | LM: 13/Jun/2009
 Critical
 F ? F : DllCall( "QueryPerformanceFrequency", Int64P,F )
 DllCall( "QueryPerformanceCounter", Int64P,pTick ), cTick := pTick
 While( ( (Tick:=(pTick-cTick)/F)) <D ) {
   DllCall( "QueryPerformanceCounter", Int64P,pTick )
   Sleep -1
 }
Return Round( Tick,3 )
}


  • Guests
  • Last active:
  • Joined: --
You probably don't have the latest version of AHK if it sees While() as a function.
<!-- m -->http://www.autohotkey.com/download/<!-- m -->

Relayer
  • Members
  • 122 posts
  • Last active: Jun 22 2015 09:21 PM
  • Joined: 24 Nov 2008
Thanks... that was it. I thought I had the latest but didn't. Silly me!

Frankie
  • Members
  • 2930 posts
  • Last active: Feb 05 2015 02:49 PM
  • Joined: 02 Nov 2008
Thanks SKAN!
This could be very useful in timing scripts aswell as comparing different AHK versions.
aboutscriptappsscripts
Request Video Tutorials Here or View Current Tutorials on YouTube
Any code ⇈ above ⇈ requires AutoHotkey_L to run

fincs
  • Moderators
  • 1662 posts
  • Last active:
  • Joined: 05 May 2007
Here's an AutoHotkey_L version of Mystiq's QPXF() function (couldn't resist):
; QPXF(<passes>, <function name>, <parameters>...)
QPXF(_P, _F, _A*)
{
   if !IsFunc(_F) || _P > 0
      return
   Loop, %_P%
      QPX(true), %_F%(_A*), T += QPX(false)
   return T / _P
}


SKAN
  • Administrators
  • 9115 posts
  • Last active:
  • Joined: 26 Dec 2005

Here's an AutoHotkey_L version of Mystiq's QPXF() function (couldn't resist):


Arbitrary number of parameters?! :O
Nice demo... Thanks.. :)

Meanwhile, I have to clarify:

There is no need for a sub wrapper for QPX().
QPX() was specifically written to test average speed of a user defined function. I guess the example in title post is not clear enough!.

While QPX( 100000 )
 Rnd := Random()
ti := QPX(0)

MsgBox, 0, 100`,000 iterations,  % "Avg. time taken for a Random Number:`t" ti " seconds"

Random() {
 Random,Rnd, 0x1,0xFFFFFFFF
Return Rnd
}

kWo4Lk1.png

Lexikos
  • Administrators
  • 9844 posts
  • AutoHotkey Foundation
  • Last active:
  • Joined: 17 Oct 2006

While QPX( 100000 )
Rnd := Random()
ti := QPX(0)

When I run that, I get a different result each time. There are two ways to make it more consistent:
[*:2ujr4xs1]Increase the number of iterations. If I run 100000*10 iterations, I get the same result every time.
[*:2ujr4xs1]Add SetBatchLines -1 so that the benchmark is less dependent on background activity (and completes faster to boot).By my estimate, two thirds (AutoHotkey_L) or three quarters (AutoHotkey Basic) of the time required for the benchmark is actually spent in QPX() and the loop itself. It is neither necessary nor wise to call the timing function once in every iteration. Instead, capture only the start and end times, then calculate the difference and divide by the number of iterations. Generally if the accuracy of A_TickCount isn't adequate, the test is too short and would not get consistent results with QPC anyway.

For example:
i := 10000000
SetBatchLines -1
begin := A_TickCount
Loop % i
{
    Rnd := Random()
}
ti := (A_TickCount-begin)/i

MsgBox, 0, %i% iterations,  Avg. time taken for a Random Number:`t%ti% ms

Random() {
    Random,Rnd, 0x1,0xFFFFFFFF
    Return Rnd
}
I estimate around 5% of the time is spent executing Loop % i { } and the rest spent executing Rnd := Random().

emmanuel d
  • Members
  • 519 posts
  • Last active: Dec 28 2013 12:48 PM
  • Joined: 29 Jan 2009
Then this would be a good example:
i := 10000000
SetBatchLines -1
;------------------------------------------the loop itself
begin := A_TickCount
Loop % i {
    L1:=1
	
	}
L := (A_TickCount-begin)/i
;------------------------------------------the built in function
begin := A_TickCount
Loop % i {
    L1:=1
    res1:=mod(15,4)
	}
ti := (A_TickCount-begin)/i-L
;--------------------------------------------my function
begin := A_TickCount
Loop % i {
    L1:=1
    res2:=mod_(15,4)
	}
ti2 := (A_TickCount-begin)/i-L
;---------------------------------------------on the fly calculation
begin := A_TickCount
Loop % i {
    L1:=1
    res2 := 15-15//4*4
	}
ti3 := (A_TickCount-begin)/i-L
;---------------------------------------------------
MsgBox, 0, %i% iterations,  Avg. time for Loop:`t`t`t%L% ms`nAvg. time for Mod():`t`t`t%ti% ms`nAvg. time for Mod_():`t`t%ti2% ms`nAvg. time for manual Mod calculation:`t%ti3% ms

Mod_(Nr,Div) {
	return Nr-Nr//Div*Div
}
It shows the difference between a built in function, a created function, and calculation on the fly.
it excludes the loop from the time.

Stopwatch emdkplayer
the code i post falls under the: WTFYW-WTFPL license

http://www.ahkscript.org/ the new forum


RHCP
  • Members
  • 1228 posts
  • Last active: Apr 08 2017 06:17 PM
  • Joined: 29 May 2006

I love qpx. I've been using it for ages to fine tune timings in a script during run time, where sub 5 ms precision is required. 

 

This is a basic function which allows for multiple timers to be kept track of. When called with no itemId, the return value is the ID for the newly created timer.


; returns elapsed time in ms 
; Can keep track of multiple events in the same or different threads 
stopwatch(itemId := 0, removeUsedItem := True)
{
	static F := DllCall("QueryPerformanceFrequency", "Int64P", F) * F , aTicks := [], runID := 0

	if (itemId = 0) ; so if user accidentally passes an empty ID variable function returns -1
	{
		DllCall("QueryPerformanceCounter", "Int64P", S), aTicks[++runID] := S
		return runID
	}
	else 
	{
		if aTicks.hasKey(itemId)
		{
			DllCall("QueryPerformanceCounter", "Int64P", End)
			return (End - aTicks[itemId]) / F * 1000, removeUsedItem ? aTicks.remove(itemId, "") : ""
		}
		else return -1
	}
}

Example:

#singleinstance force 
SetBatchLines, -1


item1 := stopwatch()
DllCall("Sleep", UInt, 50) 
elapsed1 := stopwatch(item1, False)
DllCall("Sleep", UInt, 150) 
elapsed2 := stopwatch(item1)
msgbox % "First Stop was after " elapsed1 " ms`nSecond was after a total of " elapsed2 " ms"
; mutiple items 
item1 := stopwatch()
DllCall("Sleep", UInt, 50) 
item2 := stopwatch()
DllCall("Sleep", UInt, 100)  
item3 := stopwatch()
DllCall("Sleep", UInt, 200)
 
msgbox % stopwatch(item1) "`n" stopwatch(item2) "`n" stopwatch(item3)
 
 ExitApp



evilc
  • Members
  • 340 posts
  • Last active: Oct 27 2015 11:07 PM
  • Joined: 17 Nov 2005

Hi, sorry to hijack this thread, but I have similar requirements and this code is no good for me as it uses way too many CPU cycles.

 

I think I only need a 10ms timer (I am doing Joystick polling), and I may not even *need* any changes to my code at all, I am just trying to work out the most efficient way to set up a loop with consistent timing.

 

Please see my thread here if you think you can help.



evilc
  • Members
  • 340 posts
  • Last active: Oct 27 2015 11:07 PM
  • Joined: 17 Nov 2005

Hi, I really like QPX, it has helped me immensely in my efforts to optimize HID joystick processing.

 

However, one small niggle...

It seems that if QPX is running, executing True again does not reset the counter, nor does executing false then true immediately again seem to affect the counter.

 

Basically, this code structure gives strange results:

[code=auto:0]

some func called often(){

   QPX(true)

   if (something that sometimes happens){

     ; quit this call, dont care about timing...

     return

   }

}

QPX(false)

[code=auto:0]

Basically, you have to always remember to put a QPX(false) before an return, else the next time you call the func, the timer does not reset when it executes QPX(true) again.

I guess that this is basically by design, to provide the Extended Usage. I would imagine instead of QPX(true) you could use QPX(-1) to mean "Start and reset the timer".



jNizM
  • Members
  • 928 posts
  • Last active: Dec 30 2016 12:39 PM
  • Joined: 01 Aug 2012
did you try skans latest version from qpx? QPC()

QPC + Example ==> gist.github.com
[AHK] 1.1.24.03 x64 Unicode
[WIN] 10 Pro (Version 1607 | Build 14393.447) x64
My GitHub Profile | Donations are appreciated if I could help you

evilc
  • Members
  • 340 posts
  • Last active: Oct 27 2015 11:07 PM
  • Joined: 17 Nov 2005

FYI, change QPX(n=0) to QPX(n:=0) to make QPX compatible with AHK v2.