getMilliSec - reborn

Hello
This is an implementation of getMilliSec for Snow Leopard

I am in need of doing some timing of scripts outside of the script debugger since
it may be unfair regarding the datastructures it monitors in some solutions, to
other solutions which encapsulates its datastructures and thereby gets an unfair good time,

I never had an OS9 system, but the getMilliSec allways seemed like a good solution to me
what timing concerns.

This is by no means a perfect solution, and I do not guarantee anything but a rather coarse
measurement tool, and alas for coarse measurement only. But it should be able to show you what
is faster of alternatives, with tolerance within a 100th of a second.

INSTALLATION

Thanks to Adam Bell:
First of all you should get rid of the GetMilliSec.osax from any Scripting Additions folder.
-If those are installed, they will be searched first in the “name space” of AppleScript and rewrite
the calls to the getMilliSec() handler to GetMilliSec{} and leave you in a position were this snippet won’t work.

You need to download and install timetools 0.2.1 BY Andre Berg for this script to work.
You can download timetools here.

When the timetools is installed at the path of your choice you should hardcode the path to it in getMilliSec()
The commandline switches for timetools is -ums which gives us the uptime in milliseconds.

Save the script with the hardcoded path.

USAGE

copy the contents of the script, -handler and script into the script you want to time.
Make sure the calibrate statement are run before actually getting any timings, as it calibrates getMilliSec.



timeTools's calibrate() -- initiates calculation of overhead for the getMilliSec. 
” some example code
display dialog timeTools's overhead as text ” 
set b to getMillisec()
” your code between here

set c to getMilliSec()
display dialog (c - b) as text

” the code you need to include
on getMillisec()
	set res to (do shell script "/usr/local/opt/timetools -ums" as integer -(my timeTools's overhead))
	” you must change path to your hardcoded path to timetools,
	return res
end getMillisec

script timeTools
	global overhead
	on calibrate()
		set overhead to 0 as integer
		set a to getMillisec() -- gets stuff loaded into mem for later, takes longer.
		set a to getMillisec()
		set my overhead to (getMillisec() - a)
	end calibrate
end script

EXAMPLE

I thought I should include an example of using this with Nigel Garvey’s “lotsa method” for timing.
The code is taken from Post #4 in this thread.


” © Nigel Garvey, customized by me in order to use new getMilliSec() 
timeTools's calibrate() -- initiates calculation of overhead for the getMilliSec()
main()

on main()
	set lotsa to 500
	-- Any other preliminary values here.
	
	-- Dummy loop to absorb a small observed
	-- time handicap in the first repeat.
	repeat lotsa times
	end repeat
	
	-- Test 1.
	set t to getMilliSec()
	repeat lotsa times
		-- First test code or handler call here.
	end repeat
	set t1 to ((getMilliSec()) - t) / 1000
	
	-- Test 2.
	set t to getMilliSec()
	repeat lotsa times
		-- Second test code or handler call here.
	end repeat
	set t2 to ((getMilliSec()) - t) / 1000
	
	-- More test loops here if required.
	
	-- Timings.
	{t1, t2, t1 / t2} ” > {0.028, 0.029, 0.965517241379}
end main

” the code you need to include
on getMillisec()
	set res to (do shell script "/usr/local/opt/timetools -ums" as integer -(my timeTools's overhead))
	” you must change path to your hardcoded path to timetools,
	return res
end getMillisec

script timeTools
	global overhead
	on calibrate()
		set overhead to 0 as integer
		set a to getMillisec() -- gets stuff loaded into mem for later, takes longer.
		set a to getMillisec()
		set my overhead to (getMillisec() - a)
	end calibrate
end script



Best Regards

McUsr

Thanks, McUsr! That’s got to be better than using ‘current date’ to time scripts. :cool:

Hi.

It will hopefully be easier for all the good folks with intel machines to adapt the vast number of scripts which used the GetMilliSec.osax.

Hopes you find it usable as a replacment.

Best Regards

McUsr

Works really nicely in OS X 10.5.8 as well – GetMillisec.osax did not.

There is a caveat, however. Both scripts fail if you do not first remove any old copies of GetMilliSec.osax from your ~/Library/Scripting Additions (or anywhere else you might have put them in the past). Failing that, both the Script Editor and Script Debugger apps will alter every occurrence of “GetMilliSec()” to “GetMilliSec {}”. Took me a while to sort that out because I didn’t recall that the osax was still installed on my Leopard machine (since it no longer works).

Sorry about that Adam.

They have been long gone here, -if I ever installed them. I will update the post.

Thanks.

Best Regards

McUsr

I’ll probably use it as a loadable module:

-- Adapted from a script by McUsr.
property overhead : missing value

on calibrate()
	set overhead to 0
	getMilliSec() -- gets stuff loaded into mem for later, takes longer.
	set a to getMilliSec()
	set overhead to getMilliSec() - a
end calibrate

on getMilliSec()
	-- you must change path to your hardcoded path to timetools,
	return (do shell script "/path/to/timetools -ums")
end getMilliSec

Then it can be invoked like this:

set timer to (load script file "path:to:above:script.scpt")

tell timer
	calibrate()
	set t to getMilliSec()
end tell

delay 1 -- Code to be timed!

tell timer to return (getMilliSec() - t - (its overhead)) / 1000

Note that the overhead adjustment should be made to the difference between the two shell script results, not to both results individually ” otherwise it’s the same as not making the adjustment at all.

Hello.

You are right, the subtraction of the overhead should only be done in all but the first call.
But on my machine that added overhead is only 31 milliseconds, I have a 3GHz cpu,
which means that if an empty repeat loop, executed 500 times, in pure assembly should take about
on on third of a millionth of a second. So if there is very little to be timed, then the added accuracy will lead to
grossly inaccurate results, when the object of timing take less time than the getMilliSec handler.

My basic idea was that the time it took to take the time should be regarded as taking zero milliseconds.
Then the timing results between the calls may be fictive as constant time, but the time between two calls relative to each other should be fairly accurate.

The snippet is rewritten, and should work fairly accurate. One should just remember that a result giving negative
numbers probably isn’t worth timing.


on getMillisec()
	global _overhead, _getmillicalls
	set res to do shell script "/usr/local/opt/timetools -ums"
	-- you must change path to your hardcoded path to timetools,
	if _getmillicalls > 0 then
		set res to res - _overhead 
	else
		set _getmillicalls to 1 as integer
	end if
	
	return res
end getMillisec


script timeTools	
	on reinitate() -- makes getMilliSec not subtract overhead on first call.
		set my _getmillicalls to 0
	end reinitate
	
	on calibrate()
		global _overhead, _getmillicalls
		set _overhead to 0 as integer
		set _getmillicalls to 0 as integer
		getMillisec() -- gets stuff loaded into mem for later, this first call takes longer time.
		set _getmillicalls to 0
		set a to getMillisec()
		set _overhead to (getMillisec() - a)
		set _getmillicalls to 0 -- initiates getMilliSec to not subtract the overhead on first call.
	end calibrate
end script

My version of your script from above would now be:

” set timer to (load script file "path:to:above:script.scpt")
set timer to timeTools
tell timer
	calibrate()
	set t to getMillisec()
end tell

delay 1 -- Code to be timed! {1.003}

tell timer to return (getMillisec() - t ) / 1000

script timeTools
	
	on getMillisec()
		global _overhead, _getmillicalls
		set res to do shell script "/usr/local/opt/timetools -ums"
		-- you must change path to your hardcoded path to timetools,
		if _getmillicalls > 0 then
			set res to res - (_overhead)
		else
			set _getmillicalls to 1 as integer
		end if
		
		return res
	end getMillisec
	
	on reinitate() -- makes getMilliSec not subtract overhead on first call.
		set my _getmillicalls to 0
	end reinitate
	
	on calibrate()
		global _overhead, _getmillicalls
		set _overhead to 0 as integer
		set _getmillicalls to 0 as integer
		getMillisec() -- gets stuff loaded into mem for later, this first call takes longer time.
		set _getmillicalls to 0
		set a to getMillisec()
		set _overhead to (getMillisec() - a)
		set _getmillicalls to 0 -- initiates getMilliSec to not subtract the overhead on first call.
	end calibrate
end script



The my preferred version above yours gives (1,005) as a result of timing before and after the delay 1 command above and 1.003 when referenced from a script object the way you like it.

I find those numbers good, it would have been interesting to see the originals results for the delay 1

Best Regards

McUsr

With the OSAX on my G5 (2GHz), I get various timings between 0.995 and 1.011 seconds. Your script results are very acceptable!

Instead of subtracting the overhead “in all but the first call”, a simpler approach would be to add the overhead in the first call only. It also occurs to me that the first call might include the calibration process and store its own result, so that the later call(s) simply return the time difference(s). The “first call” would need a different name in this case:

-- set timer to (load script file "path:to:above:script.scpt")
set timer to timeTools
tell timer to startMillisec()

delay 1 -- Code to be timed!

tell timer to return getMillisec() / 1000

script timeTools
	-- This will be result of the "first call", adjusted upwards.
	property adjustedStartTime : 0
	
	on getMillisec()
		return (do shell script "/usr/local/opt/timetools -ums") - adjustedStartTime
		-- you must change path to your hardcoded path to timetools,
	end getMillisec
	
	on startMillisec()
		set adjustedStartTime to 0
		getMillisec() -- gets stuff loaded into mem for later, this first call takes longer time.
		set adjustedStartTime to -(getMillisec()) + 2 * (getMillisec())
		(* -- The line above is compacted from:
		set a to getMillisec()
		set b to getMillisec()
		set adjustedStartTime to b + (b - a) *)
	end startMillisec
end script

Hello
Nigel Garvey wrote:

What he did forget to mention was that his idea would lead to a slightly more ( at least theoretical) result, as
any computation error in the getMilliSec function is then only made once. Thanks! :slight_smile:

Being Norwegian and a Norwegian is stubborn :slight_smile: I have followed my “Holy Grail” to keep it as it was, in order
for as much people to find it easy to use, with a minimum of code to modify when they want to try all the good
samples here which depended upon the old GetMilliSec call.

It is also fairly easy to use when you really only have one function call to consider. And thats important for me.

I had to be a little bit naughty in order to be stubborn, as I found N.Garveys suggestion about using two functions
very worthwhile to follow. I still wanted to get by with “the one and only handler name” :), so I’m renaming the handler within the handler but that appears to work quite well. :slight_smile: (And this is one of things I love about AS.)

I think, but I have not tested it; that it should work equally well if loaded from a script object, provided that
the script "TimeTools is included as a whole within the file to be saved as a script object.
Edit: This assumption was utterly wrong se code below which works in such a context.


-- set timer to (load script file "path:to:above:script.scpt") ” Must include embedding TimeTools script in the file!
tell timeTools to run
set t to getMillisec()

delay 1 -- Code to be timed! {1.005 is median, now getting results from 0.989 to 1.008 occasionally}
set res to (getMillisec() - t) / 1000

script timeTools
	
	on firstMillisec()
		global getMillisec
		local overhead
		timeTools's realgetMillisec() -- gets stuff loaded into mem for later, this first call takes longer time.
		set getMillisec to  timeTools's realgetMillisec
		set _overhead to -(timeTools's realgetMillisec()) + 2 * (timeTools's realgetMillisec()) ” Nigel Garvey
		return _overhead
	end firstMillisec
	
	on realgetMillisec()
		local res
		set res to do shell script "/usr/local/opt/timetools -ums"
		-- you must change path to your hardcoded path to timetools,
		return res
	end realgetMillisec
	on run
		global getMillisec
		set getMillisec to timeTools's firstMillisec
	end run
end script

Enjoy!

McUsr

I thought you might. :wink:

Hello.

I did as a matter of fact anticipate that this would work when loaded as a script object. It didn’t.
What I ended up with was a script object looking like this.


(*
TERMS OF USE. 
This applies only to posting code, as long as you don't post it, you are welcome to do
whatever you want to do with it without any further permission. 
Except for the following: Selling the code as is, or removing copyright statmentents and the embedded link in the code (without the http:// part) from the code. 

You must also state what you eventually have done with the original source. This obviously doesn't matter if you  distribure AppleScript as read only. I do not require you to embed any properties helding copyright notice for the code.

Credit for having contributed to your product would in all cases be nice!

If you use this code as part of script of yours you are of course welcome to post that code with my code in it here at macscripter.net. If you then wish to post your code elsewhere after having uploaded it to MacScripter.net please email me and ask for permission.

The ideal situation is however that you then refer to your code by a link to MacScripter.net
The sole reason for this, is that it is so much better for all of us to have a centralized codebase which are updated, than having to roam the net to find any usable snippets. Which some of us probabaly originated in the first hand.

I'm picky about this. If I find you to have published parts of my code on any other site without previous permission, I'll do what I can to have the site remove the code, ban you, and sue you under the jurisdiction of AGDER LAGMANNSRETT of Norway. Those are the terms you silently agree too by using this code. 

The above paragraphs are also valid if you violate any of my terms.

If you use this or have advantage of this code in a professional setting, where professional setting means that you use this code to earn money by keeping yourself more productive. Or if you as an employee share the resulting script with other coworkers, enhancing the productivity of your company, then a modest donation to MacScripter.net would be appreciated.
*)
script timeTools
	property parent : AppleScript
	property getMillisec : missing value
	
	on firstMillisec()
		local overhead
		my realgetMillisec() -- gets stuff loaded into mem for later, this first call takes longer time.
		set my getMillisec to my realgetMillisec
		set _overhead to -(my realgetMillisec()) + 2 * (my realgetMillisec()) -- Nigel Garvey
		return _overhead
	end firstMillisec
	
	on realgetMillisec()
		local res
		set res to do shell script "/usr/local/opt/timetools -ums"
		-- you must change path to your hardcoded path to timetools,
		return res
	end realgetMillisec
	on run
		set my getMillisec to my firstMillisec
	end run
end script

property parent : my timeTools ” Use this if  scriptobject is included as a whole.

And a client looking like this.

property timer : (load script file "Macintosh HD:Users:Me:Desktop:timerTools.scpt")
property parent : timer ” DONT use this if script is included as a whole.
tell timer to run
set t to getMillisec()

delay 1 -- Code to be timed! {1.005 is median, now getting results from 0.989 to 1.008 occasionally}
set res to ((getMillisec()) - t) / 1000

This works, I’ll try to figure out what rules I broke when I supposed that the globals would be brought forward to the calling script from the script object. ( Edit:-By now I guess it was the parent property.) But in this case no closures were introduced when replacing the global getMilliSec with a property.
-Naturally enough since the property is outside the two functions. But then again, there are things with closures that comes close to supernatural if you ask me.

Best Regards

McUsr

Do you actually want your real name in the second script?

No I didn’t Adam.

Thank you very much.

Best Regards

McUsr

Christopher Stone, on the AppleScript-Users mailing list, has just made me aware of an OSAX by Tetsuro Kurita called LapTime. It’s a universal binary and claims to be compatible with OS X 10.4 and later, so presumably it’s good for both Intel and PPC machines.

It can provide individual timers for overlapping events and each timer can, if required, return a list of “lap times”, which could be, say, the timings of every iteration of a repeat or simply the times taken by consecutive sections of a piece of code. Results are in milliseconds and on my machine, they’re given to twelve decimal places!

It would be prefererable to use this rather than timetools to time scripts, since it doesn’t carry the overheads associated with ‘do shell script’. Also, timetools is hard to find at the moment. It’s either been withdrawn or was only ever uploaded to MacScripter’s “Scripting Additions” resource, which like “Scriptbuilders” and “Automator Workflows” has been down for the past year pending migration to a new server…

Hello.

Givent the opportunities in Maverick’s (which is the most pleasant OS ever) new addtions to scripting, I have rewritten GetMilliSec using the new library technology.

Save the Script below in the the ~/Library/Script Libraries folder, as a bundle, with the name TimerTools.scptd ,be sure to open the drawer and check off for “AppleScript/Objective-C library”.

-- © McUsr and put into public domain.

use AppleScript version "2.3"
use scripting additions
use framework "Foundation"
use framework "AppKit"

on getMillisec()
	tell current application's NSDate to ¬
		return ((its timeIntervalSinceReferenceDate()) * 1000)
end getMillisec

The cript below is an example of usage, once you have stored the TimerTools.scptd in the Script Libraries folder.

use AppleScript version "2.3"
use scripting additions
use McUsr : script "TimerTools"

set a to McUsr's getMillisec()

Edit

It should now be more precise, thanks to Shane Stanley.

Enjoy :slight_smile:

When you’re not using a custom terminology (read:scripting definition file) you don’t need to save it as a scripting bundle.

Hello.

For some reason, that doesn’t work for me.

(I’m told that it can’t find the Foundation Framework then.

-I’m editing with AppleScript Editor, and not running AsObj-C Runner, which I guess smooths things over.)

My Mistake, you’re right (it’s a bit early :slight_smile: ). It needs the OSAAppleScriptObjCEnabled key set to true, which isn’t the case for Script files, only for Script Bundles.

Hello.

It’s a quick mistake. I’d also state here and now, that I thought through whether I’d implement a scripting definition file or not. :slight_smile: I landed on not, since this a single handler, that I may incorporate into something later. And, as a single handler, to be used for development, I really reserve me the right to not experience any terminology clashes at a later date. :slight_smile:

You’ll probably save yourself a fraction of one of those milliseconds if you just use the class method timeIntervalSinceReferenceDate(), skipping the alloc and init. And 1970 isn’t the reference date for OS X; 2001 is.

FWIW, I hummed and harred, and finally added an optional time stamp to AS log entries in ASObjC Explorer. The overhead of a log call on my Mac is about 0.00012 on my iMac, so it makes a reasonable timer.