First off, running JS from AS seems like a weird thing to do, unless there’s no alternative – why not simply write everything in JS?
Regardless, I rewrote your benchmarks into a single (Apple!)script which permits everyone to simply test it on their machines without any additional tools.
The code
use framework "Foundation"
use framework "JavaScriptCore"
use scripting additions
set counter to 1000
set {a, b} to {2, 3}
set code to quoted form of ("((a,b) => a+b)(" & (a as text) & "," & (b as text) & ")")
set startTime to current application's NSDate's now
repeat counter times
set c to a + b
end repeat
set thetime to ((startTime's timeIntervalSinceNow()) * -1000) as integer
log "pure AS: " & thetime & "ms"
set startTime to current application's NSDate's now
repeat counter times
set c to run script code in "JavaScript"
end repeat
set thetime to ((startTime's timeIntervalSinceNow()) * -1000) as integer
log "run script: " & thetime & "ms"
set a to current application's NSNumber's numberWithInt:2
set b to current application's NSNumber's numberWithInt:3
set startTime to current application's NSDate's now
repeat counter times
set c to ((a's intValue()) + (b's intValue()))
end repeat
set thetime to ((startTime's timeIntervalSinceNow()) * -1000) as integer
log "NSInteger: " & thetime & "ms"
tell application "Safari"
set startTime to current application's NSDate's now
repeat counter times
set c to (do JavaScript "2+3" in document 1) as integer
end repeat
set thetime to ((startTime's timeIntervalSinceNow()) * -1000) as integer
log "Safari: " & thetime & "ms"
end tell
set addNumbers to "((a, b) => a+b)(2,3)"
set theJSContext to current application's JSContext's new()
set startTime to current application's NSDate's now
repeat counter times
set theEvaluateScript to ((theJSContext's evaluateScript:addNumbers)'s toObject() as integer)
end repeat
set thetime to ((startTime's timeIntervalSinceNow()) * -1000) as integer
log "JSCore: " & thetime & "ms"
The output for me is
(*pure AS: 0ms*)
(*run script: 5650ms*)
(*NSInteger: 60ms*)
(*Safari: 921ms*)
(*JSCore: 86ms*)
(I didn’t bother with do shell osascript
). This is for 1000 runs for each step. I factored out all setup (for example, running set {a,b} to {2,3}
amounts to 2ms on my machine!). I’d think that this is a reasonable approach if the goal is performance. And I permitted myself to re-write the JS functions so that they presumably run fastest. Same reasoning as before.
As can be clearly seen, run script
is crap and one should forget about it. Safari is not a good choice, either – firstly, it has to be started, secondly, a document has to be open in it.
Now to the behavior of the algorithms, i.e. the O(n) characters.
-
pure AS
100/1000/5000: 0/0/2
-
run script
100/1000/5000: 789/5650/34675 ⇒ 7.9/5.7/6.9 ms per run
-
NSInteger
100/1000/5000: 6/60/249 ⇒ 0.06/0.06/0.05 ms per run
-
Safari
100/1000/5000: 124/921/7842 ⇒ 1.24/0.92/1.57 ms per run
-
JSCore
100/1000/5000: 10/86/546 ⇒ 0.1/0.09/0.11 ms per run
All numbers are to be taken with a grain of salt. What is obvious, though, is that Safari’s runtime behavior is the most erratic. But all algorithms perform roughly in O(n) (anything else would be a disaster).
Why is a runtime of 16ms relevant? I’m all for optimization, but doing that is only reasonable if
- a script/program is running “too slow” (for whatever meaning of “slow”)
- the bottlenecks can be clearly identified.
Shaving off 3 milliseconds of something that is run four times is irrelevant. Shaving of 1 millisecond of something that is run 1 million times is relevant. If there are tools that do performance measurements at statement and procedure level for AS, one should use them to find the bottlenecks. If there are no such tools, one has to find other means to determine the pain points.
Exactly. In the thread you mentioned, we were talking about getting files and folders recursively. Some people suggested using find
because that’s a one-liner. And the task is one-time. So, one can easily compare the time needed to write a complex ASObJ script with writing a one-liner find
– I’d wager that the difference far outweighs the perceived or real delay caused by running a shell command as opposed to running an ASObjC script.
Do not optimize things that don’t need optimization. That’s just a waste of resources.
That would only make sense if what they do in the tell
blocks were optimized. If you have a repeat
loop inside the tell
that runs for 2 seconds, it’s far more important to optimize that than fiddle around with the tell
.
See my other post for a different approach (i.e. a more relevant functionality and deeper testing)