Inspired (or triggered?) by a recent post here I decided to run my own benchmarks on a more relevant task, namely sorting an array of (random) numbers.
The AppleScript code
use framework "Foundation"
use scripting additions
global arrayLength
global l
set arguments to (current application's NSProcessInfo's processInfo's arguments) as list
if count of arguments is 5 then
set arrayLength to (item 5 of arguments) as number
else
set arrayLength to 10000
end if
on setup()
set l to {}
repeat with i from 1 to arrayLength
set end of my l to random number arrayLength
end repeat
end setup
on qsort(theList, l, r)
script o
property cutoff : 10
property p : theList
on qsrt(l, r)
set i to l
set j to r
set v to my p's item ((l + r) div 2)
repeat while (j > i)
set u to my p's item i
repeat while (u < v)
set i to i + 1
set u to my p's item i
end repeat
set w to my p's item j
repeat while (w > v)
set j to j - 1
set w to my p's item j
end repeat
if (i > j) then
else
set my p's item i to w
set my p's item j to u
set i to i + 1
set j to j - 1
end if
end repeat
if (j - l < cutoff) then
else
qsrt(l, j)
end if
if (r - i < cutoff) then
else
qsrt(i, r)
end if
end qsrt
on isrt(l, r)
set x to l
set z to l + cutoff - 1
if (z > r) then set z to r
set v to my p's item x
repeat with y from (x + 1) to z
if (my p's item y < v) then
set x to y
set v to my p's item y
end if
end repeat
tell my p's item l
set my p's item l to v
set my p's item x to it
end tell
set u to my p's item (l + 1)
repeat with i from (l + 2) to r
set v to my p's item i
if (v < u) then
set my p's item i to u
repeat with j from (i - 2) to l by -1
if (v < my p's item j) then
set my p's item (j + 1) to my p's item j
else
set my p's item (j + 1) to v
exit repeat
end if
end repeat
else
set u to v
end if
end repeat
end isrt
end script
set listLen to (count theList)
if (listLen > 1) then -- otherwise the handler will error
-- Translate negative indices
if (l < 0) then set l to listLen + l + 1
if (r < 0) then set r to listLen + r + 1
if (r = l) then
-- No point in sorting just one item
else
-- Transpose transposed indices
if (l > r) then
set temp to l
set l to r
set r to temp
end if
if (r - l < o's cutoff) then
-- Skip the Quicksort if cutoff or less items
else
o's qsrt(l, r)
end if
o's isrt(l, r)
end if
end if
return -- nothing
end qsort
setup()
log arrayLength as string & " runs"
set startTime to current application's NSDate's now
qsort(l, 1, -1)
set thetime to ((startTime's timeIntervalSinceNow()) * -1000) as integer
log "pure AS: " & thetime & "ms"
setup()
set my text item delimiters to character id 0
set startTime to current application's NSDate's now
set c to run script "`" & l & ".split('\\x00').sort((a,b) => a-b))`" in "JavaScript"
set thetime to ((startTime's timeIntervalSinceNow()) * -1000) as integer
log "run script: " & thetime & "ms"
setup()
set NSl to current application's NSArray's arrayWithArray:l
set startTime to current application's NSDate's now
set c to NSl's sortedArrayUsingSelector:"compare:"
set thetime to ((startTime's timeIntervalSinceNow()) * -1000) as integer
log "NSArray: " & thetime & "ms"
setup()
set sortArray to "const l = '" & l & "';l.split('\\x00').sort((a,b) => a-b)"
set theJSContext to current application's JSContext's new()
set startTime to current application's NSDate's now
--set c to ((theJSContext's evaluateScript:sortArray)'s toObject() as list)
set c to ((theJSContext's evaluateScript:sortArray)'s toArray() as list)
set thetime to ((startTime's timeIntervalSinceNow()) * -1000) as integer
log "JSCore: " & thetime & "ms"
setup()
set sortArray to "const l = '" & l & "';l.split('\\x00').sort((a,b) => a-b)"
set theJSContext to current application's JSContext's new()
set startTime to current application's NSDate's now
--set c to ((theJSContext's evaluateScript:sortArray)'s toObject() as list)
set c to theJSContext's evaluateScript:sortArray
set thetime to ((startTime's timeIntervalSinceNow()) * -1000) as integer
log "JSCore, no cast: " & thetime & "ms"
The JavaScript code
(() => {
const args = $.NSProcessInfo.processInfo.arguments;
const arrayLength = args.count == 5 ? +args.js[4].js : 10000;
const l = [];
for (i = 0 ; i < arrayLength; i++) {
l[i] = Math.floor(Math.random() * 10000);
}
const startTime = Date.now();
l.sort((a,b) => a-b);
console.log(`${arrayLength} numbers sorted in ${Date.now() - startTime} ms`);
})()
Remarks
- If the scripts are saved each to a separate file, they can be run by
osascript -l <language> <scriptFile> <count>
.<language>
is “AppleScript” or “JavaScript”,<count>
is the number of elements in the array/list to sort - The scripts output the timing results to standard error using
log
(AppleScript) andconole.log
(JavaScript) - The array is set up anew before each different method to sort it for the AppleScript stuff
- The qsort algorithm in the “pure AS” implementation is stolen from a post by @Nigel_Garvey here, from about 2012.
For the timing, I ran every script 10 times with the same array size like so repeat 10 { osascript -l AppleScript sort_bench.applescript 20000 } 2>&1 | sort
in macOS’ zsh
shell. This proved to give more stable results (aka a smaller standard deviation) than running them in Script Editor. In the latter, the results for the same method varied widely.
The results
1000 elements
pure AS: 296ms / 0.3
run script: 100ms / 0.1
NSArray: 0ms / 0
JSCore: 3.2ms / 0.003
JSCore, no cast: 2ms / 0.002
JS: 1.4ms / 0.001
5000 elements:
pure AS: 394ms / 0.08
run script: 210ms / 0.04
NSArray: 2ms / 0.0004
JSCore: 16ms / 0.0032
JSCore, no cast: 10ms / 0.002
JS: 7ms / 0.0014
10000 elements:
pure AS: 569ms / 0.057
run script: 346ms / 0.035
NSArray: 4ms / 0.0004
JSCore: 35ms / 0.0035
JSCore, no cast: 21ms / 0.0021
JS: 12ms /0.0012
15000 elements:
pure AS: 660ms / 0.044
run script: 433ms / 0.029
NSArray: 7ms / 0.0005
JSCore: 49ms / 0.0033
JSCore, no cast: 31ms / 0.0021
JS: 19ms / 0.0013
20000 elements:
pure AS: 724ms / 0.036
run script: 587ms / 0.030
NSArray: 9ms / 0.0006
JSCore: 69ms / 0.0035
JSCore, no cast: 45ms / 0.003
JS: 27ms / 0.0018
It is quite obvious from the numbers that sorting an array in ObjC using NSArray
is the fastest method, followed by the pure JavaScript sort()
method.
NoteThat is, as @Nigel_Garvey points out below, partially due to the fact that the conversion from an AS list
to a NSArray
is not measured – this takes sometimes longer than the sort itself.
ObjC is about five times as fast as JS here. Next comes running the JavaScript in JSCore, which needs considerably time to coerce the result back to a list
(compare the results for JSCore
and JSCore, no cast
). As was to be expected, pure AppleScript was the slowest method. Not so expected (after the results mentioned in the other thread) is that run script
is faster than the pure AS variant.
I didn’t bother with running JS in Safari – too far-fetched for my taste.