AppleScript, ASObjC vs JavaScript

If we do some test in Script Geek and test simple function to add 2 numbers.

The performance for AppleScript and AppleScriptObjC is less and 1 ms. (incl JavaScriptCore)
The performance with run script in “JavaScript” is 3 ms.
The performance in tell block using do JavaScript is 4 ms. (is not stable)

In this simple example show that AppleScript or AppleScriptObjC is 3 times faster.
We learn that run script is faster and tell block. It could be that JavaScript is faster
to run from extended runtime like node.JS and use IPC to return the value as AppleScript.
We also learn that JavaScriptCore is faster and run script or tell block. In other words we use
AppleScriptObjC to call JavaScript and return its result.

It show that run script, osascript or tell block to run JavaScript is slower compared to other.

use framework "Foundation"
use scripting additions

set a to current application's NSNumber's numberWithInt:2
set b to current application's NSNumber's numberWithInt:3
return ((a's intValue()) + (b's intValue()))
set {a, b} to {2, 3}
return a + b
run script "function add(a,b) {return a+b}add(2,3)" in "JavaScript"
tell application "Safari"
	(do JavaScript "function add(a,b) {return a+b}add(2,3)" in document 1) as integer
end tell
use framework "Foundation"
use framework "JavaScriptCore"
use scripting additions


set addNumbers to "function add(a, b) {
	return a + b;
}
add(2,3)"

set theJSContext to current application's JSContext's new()
set theEvaluateScript to ((theJSContext's evaluateScript:addNumbers)'s toObject() as integer)
1 Like

Running benchmarks on a single execution does not tell you anything. I’d suggest running the code at least 1000 times and dividing the resulting milliseconds reading by 1000 if you wish to.

As to your JS examples: There’s no reason to execute an addition in a function. This code, run in Script Editor, terminates in 0 ms:

(() => {
const a = 1;
const b = 2;
const count = 1000;
const startTime = Date.now();
for (i = 0; i < count; i++) {
  const c = a+b;
}
console.log(Date.now() - startTime)
})()

Increasing count to a million makes it run in 28 ms.
The equivalent AS code runs in 263 ms in Script Editor, Script Geek reports 240, so the magnitude is comparable (and it’s 10 times slower than JS).

use framework "Foundation"
set counter to 1000000
set {a, b} to {2, 3}

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 theTime

Personally, I find this kind of comparison only mildly interesting. First off, addition is not an interesting operation, since it’s optimized so much, runs very fast and is, in general, not a bottleneck. Secondly, if one insists on comparing unimportant operations, it should be done in a similar environment. Having AS execute in Script Editor (presumably) and JS in run script (uselessly, given that Script Editor runs it just fine) or (worse) Safari, or (the worst) in JSCore doesn’t tell you anything. Thirdly, as I mentioned previously, timing a single operation doesn’t tell you anything at all due to measurement inaccuracies. Fourthly, if you have to use JS from AS, you should use a long-running operation to execute so that you can determine the time needed for setup and teardown of the environment (run script, Safari, JSCore). And even then, the whole timing thing is only relevant if you call the JS code often. A single call doesn’t matter anyway.

What is the accuracy of the timers that are used here? - JS Date.now() returns milliseconds, as does NSDate. Consequently, saying that an operation run in “1 ms” is meaningless – we don’t know if it took 0.1 ms or 0.9 ms, since 1 is the smallest number anyway.

More interesting comparisons would involve filtering/sorting/mapping arrays (lists), for example.

And, seriously: pure JavaScript (without interaction with apps) will almost certainly be faster than AS simply because the JS engines are optimized beasts. Google/MS/Apple are working on them continuously because they want to have fast browsers. I doubt that Apple invests any work in making AS run fast(er).

The main differences will probably be seen when interacting with applications, like pasting into Numbers or whatever. Given Apple’s poor record of integrating JavaScript into its scripting architecture, AS should have performance advantages there.

2 Likes

I ran the code in Script Editor and got the following error message. I changed the default language in Script Editor settings to Javascipt (1.1) but still got the error message. Do I need to do anything to get the code to run? Thanks!

Expected expression but found “>”.

Runs just fine here:

I see the same error message when the language is set to “AppleScript” in the top bar. The default language is not relevant here.

Thanks–I had the navigation bar hidden but I changed that and everything works now.

@chrillek
tell block, scripting command (do shell script, run script…) use IPC to call other scripting language
to execute and return (AppleScript value). Any do shell script perform shell command or script
and return string.

(do shell script "expr 2 + 3") as integer

Still we get 4 ms to execute that command. In slower machines this will be higher.
The context of this approach is to show different ways to return AppleScript integer value by adding 2 numbers. If we have NSNumber object we need to use a property to get its value. AppleScript will
automatic convert this value to right type.

And if we call the same expr function in terminal we will get less 1 ms execute time.

In other words on my machine to use one do shell script call, I will add 4 ms to execute time.
If my function is faster and 4ms I will not save any performance by calling a do shell script.
And the only way to know that is to perform some test on that function.

@chrillek
You could share your JavaScript knowledge in this forum by using Adobe’s UXP :slight_smile:

That is one reason for people to use JavaScript and properly an better approach and JXA.

Thanks for the suggestion. But that’s not for me

  • I don’t have the software
  • I don’t find the questions interesting
  • I try to avoid Adobe as much as I can (think Flash)

So, I’ll have to do other things, like comparing apples and oranges.

I know that. And it’s interesting though not complete yet.
But that still doesn’t convince that calling JavaScript from AppleScript to add two numbers is in any way interesting. All this comparing 1 to 4 ms would be relevant if there could would run at least a few hundred times. Which I doubt it will.

@chrillek
Some people like to run JavaScript from AppleScript. That means the user has to use default scripting language AppleScript. Your example didn’t do that. In AppleScript scripting command a user could run other scripting language.

If a function I like to execute on my machine is less 4ms. It means if I chose to run the same function from other scripting language by using scripting command. I will add 4ms to the execution
and for every time its execute.

If the user has 4 x do shell script in the script it will add 4 x 4ms to the total execution.

Your point in other topic why use AppleScriiptObjC when a user could do the same thing with unix command find. What you didn’t know is that execution of do shell script is different for every user and on a slow machine it could be its slower. You argument was why use AppleScriptObjC and make more complex code to do something that is less complex.

This topic is about understanding the difference and maybe understand why some AppleScript is slower and other solutions. Let say the performance of tell block is 4ms and user have many it will make the script to become slower. The same goes if the user have many scripting commands.

And your example didn’t use AppleScript language and thats why you never could return AppleScript value.

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)

1 Like