Question about large difference in execution speed

Adam,

I’m not sure if you were joking about the “beep/display dialog” example :slight_smile: . But to really see the difference in execution speed, try the “resize window” example. Save the code below as an applet and execute it with Finder (double-click). You’ll find that it executes quickly. Now change the line “run script the_script” to “run the_script”, resave it as an applet, execute it again with Finder, and you’ll see how slowly it executes. Another way to get it to execute slowly, as Nigel and I have been observing, is to remove the lines “run script the_script”, “script the_script”, and “end script”; ie, remove the script object, and convert it to vanilla code. Once again, save it as an applet, and execute it with Finder. Slow!!! The common theme to all this is: for fast execution speed, an applet must be executed by “run script” (or by clicking the Run command in Script Editor, which I presume does the same thing as “run script”).

bmose


-- CREATE A SAMPLE TEXTEDIT DOCUMENT

tell application "TextEdit"
	activate
	make new document with properties {text:"Newton's Three Laws of Motion:

I. Every object in a state of uniform motion will remain in that state of motion ...

II. Force equals mass times acceleration.

III. For every action there is an equal and opposite reaction."}
end tell

-- RESIZE THE WINDOW

run script the_script

script the_script
	tell application "System Events" to tell process "TextEdit"
		set frontmost to true
		tell window 1
			tell scroll area 1 to if exists scroll bar 2 then keystroke "w" using {command down, shift down} -- if the window opens in "Wrap to Page" mode, change to "Wrap to Text"			
			click (first button whose subrole is "AXZoomButton") -- start with a fully zoomed window
			set {x_max, y_max} to size -- get the starting size of the fully zoomed window
			repeat with xy_axis in {"y", "x"} -- resize the vertical then the horizontal dimension of the window
				if contents of xy_axis is "x" then -- set the maximum and minimum amounts (delta) by which the window will be resized with each iteration, as well as switches to apply the resizing to the horizontal or vertical dimension selectively
					set {delta_max, delta_min, x_delta_switch, y_delta_switch} to {-x_max / 2, 10, 1, 0}
				else if contents of xy_axis is "y" then
					set {delta_max, delta_min, x_delta_switch, y_delta_switch} to {-y_max / 2, 10, 0, 1}
				end if
				set delta_value to delta_max -- set the starting delta to 1/2 the dimension of the fully zoomed window
				repeat
					set abs_delta_value to delta_value * (1 - 2 * ((delta_value < 0) as integer)) -- absolute value of delta
					tell scroll area 1 to tell scroll bar 1 to if enabled then -- if scroll bar is enabled, window size is too small
						if delta_value = delta_max then exit repeat -- quit if fully zoomed window is too small
						if delta_value < 0 then set delta_value to delta_value * -1 -- otherwise, be sure the next window resizing is in the optimal direction
					else -- if scroll bar is not enabled, window size is either optimal or too big
						if abs_delta_value < delta_min then exit repeat -- quit if window size is optimal
						if delta_value > 0 then set delta_value to delta_value * -1 -- otherwise, be sure the next window resizing is in the optimal direction
					end if
					set {x, y} to size
					set size to {x + delta_value * x_delta_switch, y + delta_value * y_delta_switch} -- resize the window
					if abs_delta_value > delta_min then set delta_value to delta_value / 2 -- halve the amount by which the window will next be resized, but no smaller than the minimum amount
				end repeat
			end repeat
			set {x, y} to size
			set position to {round (x_max - x) / 2 rounding down, round (y_max - y) / 2 rounding down} -- center window on screen
		end tell
	end tell
end script

kel,

Even if my window resizing program is saved as a stay open applet, unless the “run script” command is present, despite fast start-up time, ongoing execution speed is S–L–O–W. Try it. Take out the lines “run script the_script”, “script the_script”, and “end script”, save it as a stay open applet, then send run commands to it. You’ll see how slowly it executes each time! “run script” must be there!

bmose

Damn - I’ve deleted my answer twice :mad:, but I wasn’t joking at all, bmose. Consider this example comparing the two, I get 10 to 1 for an even simpler example, but opposite what you’re saying (unless I’ve screwed up):


set Diff to lotsa(500) --> {10.256, 0.002, 5128.0, 1.95007800312012E-4}

on lotsa(many)
	-- Any other preliminary values here.
	
	-- Dummy loop to absorb a small observed
	-- time handicap in the first repeat.
	repeat many times
	end repeat
	
	-- Test 1.
	set t to GetMilliSec
	repeat many times
		-- Second test code or handler call here.
		script the_script
			set A to "Now is the Time"
		end script
		
		run script the_script
		
	end repeat
	
	set t1 to ((GetMilliSec) - t) / 1000
	
	-- Test 2.
	set t to GetMilliSec
	repeat many times
		-- First test code or handler call here.
		script tscript
			set A to "Now is the Time"
		end script
		
		run tscript
	end repeat
	
	set t2 to ((GetMilliSec) - t) / 1000
	
	-- Timings.
	return {t1, t2, t1 / t2, t2 / t1}
end lotsa

Adam,

The reason you got opposite results is that you asked a different question from the one Nigel and I have been addressing. You’re script tests the overhead in setting up “run script” vs “run” many times. Clearly there is more overhead in setting up “run script” than “run”. However, we have been asking the question: Once “run script” or “run” (or plain vanilla code, for that matter) is set up, how fast does the code within the script (or the plain vanilla code) execute, and specifically when the code contains commands sent to other applications. What you’ll find is that when the code contains commands sent to other applications, and the applet is opened by the Finder (eg, by double-clicking), wrapping the code in a single all-encompassing script object and then running that script object with a single “run script” command executes 7 to 10 (or more) times faster than running it with “run” or executing it as plain vanilla code without any script object.

bmose

Here a command is issued repeatedly to Finder to get its own name as a simple example of sending a command to another application:


set Diff to lotsa(5000)
display dialog "run script:  " & (item 1 of Diff as string) & " seconds" & return & ¬
	"run:  " & (item 2 of Diff as string) & " seconds" & return & ¬
	"vanilla:  " & (item 3 of Diff as string) & " seconds"
--> run script: 8 seconds, run: 82 seconds, vanilla: 97 seconds
--> over a 10-fold improvement in execution speed with "run script" !!

on lotsa(many)
	-- Dummy loop to absorb a small observed
	-- time handicap in the first repeat.
	repeat many times
	end repeat
	
	-- Test 1.
	set t to (current date)
	
	script script_1
		repeat many times
			tell application "Finder" to get name
		end repeat
	end script
	run script script_1
	
	set t1 to ((current date) - t)
	
	-- Test 2.
	set t to (current date)
	
	script script_2
		repeat many times
			tell application "Finder" to get name
		end repeat
	end script
	run script_2
	
	set t2 to ((current date) - t)
	
	-- Test 3.
	set t to (current date)
	
	repeat many times
		tell application "Finder" to get name
	end repeat
	
	set t3 to ((current date) - t)
	
	-- Timings.
	return {t1, t2, t3}
end lotsa

Hi bmose,

You have to open the stay open script first (like at startup) and have it sit there not doing anything. Since it’s already open, it’s instantaneous when run.

gl,

kel,

I did exactly that and still get the dramatic difference in execution speeds. Even though the stay open applet starts up immediately when run, it performs the window resizing very slowly unless it contains an embedded “run script” command. I believe you will find the same result if you try your technique with my window-sizing program (the updated version being the one I posted a couple of hours ago). If the code is wrapped in a script object and that script object is run with an embedded “run script” command within the applet, it executes quickly. If it is converted to plain code by removing the “run script the_script”, “script the_script”, and “end script” commands, it runs 7-10 times more slowly. I would love to hear your results with each of these two versions of the stay open applet.

bmose

Phew! Go to visit my Mum for the afternoon and the whole thread goes haywire! :rolleyes:

‘run’ can be either an AppleScript or an application command. If it’s not addressed to an application, it’s an AppleScript command that can be used to run script objects. Being an AppleScript command, it kicks in almost instantly. ‘run script’, on the other hand, is a command from the StandardAdditions that can be used to run script objects, script text, or script files. It carries the time overhead of being an OSAX call, but once it’s going, it apparently distributes application commands faster than does ‘run’.

The problem that bmose and I have been discussing is that applets run more slowly than compiled scripts when they address commands to applications. This happens whether the applet’s double-clicked in the Finder or even run from Script Menu. A stay-open applet that’s already open may save time by not needing to be launched, but it’s still an applet. It executes script code directed at applications more slowly than would a compiled script. However, if an applet hands over the responsibility for running the code to ‘run script’, the execution speed is very similar to that of a compiled script.

Hi bmose;

The danger of “lurking”. :rolleyes: Thanks for the correction - now I understand what you three were pursuing.

Hi guys,

From similar feelings as Nigel (even without visiting my mum) I’ve been experimenting with the original TextEdit script, executing it in its original and the “run script” variants, running from Script Editor and speed differences are only 60-70%. Less dramatic than the applet differences that bmose found…

So there seems to be something with applets - something more different than running script objects versus scripts. I guess that execution of applets - especially if they depend on calls to other apps - has issues outside the Applescript context: it is challenging the OS with lots of interapp communication that the OS handles with its own mechanims, different Unix priorities (with applets apparently having low priority) etc.

I can prove this with the Finder-name multiple calls script that I tried in different forms:


set dd to current date
set Diff to lotsa(500)
return (current date) - dd

on lotsa(many)
	script script_2
		set i to 1
		repeat many times
			-- optional:   ignoring application responses
				tell application "Finder" to get name
				tell application "Finder" to set i to i + 1  --verify if Finder is still working
			-- optional:   end ignoring
		end repeat
		say i as string -- yes, it does
	end script
	run script_2
end lotsa

With the “ignoring” statement, the script is freed from waiting for Finder’s confirmation - and I get the same big speed differences that bmose found, differences whose nature is NOT (just) from running scrips versus script objects but merely stem from interapp communications…

Eelco,

I got the same results that you did. Executing your code as an applet by double-clicking in the Finder and using 15,000 repetitions in the repeat loop to really put it through the grinder, I got:

run script_2 + considering application responses → 345 seconds
run script_2 + ignoring application responses → 18 seconds
run script script_2 + considering application responses → 21 seconds
run script script_2 + ignoring application responses → 18 seconds

Is it possible that the “run script” scripting addition command either ignores application responses (I doubt it) or somehow results in faster interapp communications? In the above example, the speed difference was almost 20-fold.

bmose

:lol:

Hi, Eelco. Thanks for your input.

I get similar results to bmose’s, although I changed the test conditions slightly. I ran fewer iterations, used the GetMillisec OSAX for the timings, and commented out the ‘say’ command so that it wouldn’t distort the results. For 1000 iterations on my 2 GHz G5 DP, averaged to the nearest one hundredth of a second, I get:

So using ‘ignoring application responses’ does demonstrate that applets run faster when freed from having to wait for targeted applications to respond. However, before any casual readers are tempted to use it to speed up their scripts, I should point out that it does literally cause application responses to be ignored. The Finder’s name isn’t returned to the script when it’s used. However, the name is returned when the ‘run script’ workaround is used instead.

Nigel,


run script script_2: 0.955
run script script_2, ignoring application responses: 0.812

That IS a peculiar outcome. It would imply that the “run script scriptObj” variant makes interapp inefficiencies (almost) irrelevant, which is in contrast of my idea’s. Could be interesting if you’d repeat that with a busy Finder (copying over a network, many open windows, or with 50+ items on your desktop. It may be insightfull to observe Activity Monitor for notorious heavyload subprocesses such as Window Manager)
I have to dig into that later this weekend, at home.

bmose: I actually stopped th practical use of stay-open applets as I found them very unreliable with calls to other apps. Execution in my case would tend to stall over time, without any reason.
Response and idle times would go vary - in contrast to return variable settings & theory.
I never checked memory consumption, but it might be a cause.
I realize this doesn’t sound very scientific - but sometimes it seems applets are getting tired and just give up…
So for me, the fastest & most reliable setup would be a “run script scriptObj” call that is timed externally.
Only drawback I found is that scripting errors in heavily nested script objects (i.e. calling other scripts objects) sometimes do not report themselves all the way up the hierarchy.

What is the hidden gem buried inside “run script”?

The equal part for all variants, at compilation time, is that the script object and script collect all info they can get on resources, properties, location & AS dictionary of apps that are called etc. during compilation and store all its dependencies “hardcoded” in themselves.
This stuff is readily assembled in machine language and doesn’t need to be mobilised at runtime. That’s the similar part - I don’t see any reason for differences here.

Where differences from execution speed can come from:

  • how Unix’s Task Manager works - how the OS executes code in chunks amongst other tasks.
    It may be that scripts get sliced, but script objects not (or the other way around, or differently)
  • Priorities in execution may be different from running script objects, in relation with:
  • Interapp inefficiencies and underlying dependencies (offering cpu the opportunity to do other tasks)
    for instance, my MacBook has fairly lame graphics, hampering all variants, which explains the smaller speed differences here.
  • multiple threads that might execute some parts in parallel (offering cpu the opportunity to do other tasks) but the outcome can depend on a single bottleneck. In your example, Window Manager will be busy smacking around TextEdit’s window, while other parts executed in the meantime but then have to wait for it.

As you see, we’re getting in the dark of Unix Task Manager here, that I do not fully understand either.
Closely watching Activity Monitor for threads, calls and memory, as you repeat your experiments might give some insight…

I tried the resize-TextEdit-window script and it is pretty amazing how much faster it is. I decided to try this approach on a script I have but i am having trouble. The script is saved as an application to be used as a droplet so the script starts with “on open” and then calls a subroutine. I thought I could turn the subroutine into a script and use run script but I get an error that “variable the_script is not defined”. I adapted the script used earlier here to show basically how I am trying to use the run script syntax. Anyone have any idea how I can rework this to make it work??

Save this as an application and then drag and drop a text file onto the script icon:

on open these_items
	repeat with i from 1 to the count of these_items
		set this_item to (item i of these_items)
		run script the_script
	end repeat
	tell application "Finder"
		activate
		display dialog ("Finished!") buttons " " giving up after 1
	end tell
end open

script the_script
	tell application "TextEdit"
		activate
		open this_item
	end tell
	tell application "System Events" to tell process "TextEdit"
		set frontmost to true
		tell window 1
			tell scroll area 1 to if exists scroll bar 2 then keystroke "w" using {command down, shift down} -- if the window opens in "Wrap to Page" mode, change to "Wrap to Text"			
			click (first button whose subrole is "AXZoomButton") -- start with a fully zoomed window
			set {x_max, y_max} to size -- get the starting size of the fully zoomed window
			repeat with xy_axis in {"y", "x"} -- resize the vertical then the horizontal dimension of the window
				if contents of xy_axis is "x" then -- set the maximum and minimum amounts (delta) by which the window will be resized with each iteration, as well as switches to apply the resizing to the horizontal or vertical dimension selectively
					set {delta_max, delta_min, x_delta_switch, y_delta_switch} to {-x_max / 2, 10, 1, 0}
				else if contents of xy_axis is "y" then
					set {delta_max, delta_min, x_delta_switch, y_delta_switch} to {-y_max / 2, 10, 0, 1}
				end if
				set delta_value to delta_max -- set the starting delta to 1/2 the dimension of the fully zoomed window
				repeat
					set abs_delta_value to delta_value * (1 - 2 * ((delta_value < 0) as integer)) -- absolute value of delta
					tell scroll area 1 to tell scroll bar 1 to if enabled then -- if scroll bar is enabled, window size is too small
						if delta_value = delta_max then exit repeat -- quit if fully zoomed window is too small
						if delta_value < 0 then set delta_value to delta_value * -1 -- otherwise, be sure the next window resizing is in the optimal direction
					else -- if scroll bar is not enabled, window size is either optimal or too big
						if abs_delta_value < delta_min then exit repeat -- quit if window size is optimal
						if delta_value > 0 then set delta_value to delta_value * -1 -- otherwise, be sure the next window resizing is in the optimal direction
					end if
					set {x, y} to size
					set size to {x + delta_value * x_delta_switch, y + delta_value * y_delta_switch} -- resize the window
					if abs_delta_value > delta_min then set delta_value to delta_value / 2 -- halve the amount by which the window will next be resized, but no smaller than the minimum amount
				end repeat
			end repeat
			set {x, y} to size
			set position to {round (x_max - x) / 2 rounding down, round (y_max - y) / 2 rounding down} -- center window on screen
		end tell
	end tell
end script

Model: G5 Tower (not Intel) - Script Editor ver 2.1.1
AppleScript: Tiger
Browser: Safari 419.3
Operating System: Mac OS X (10.4)

Put the script first, and declare this_item as a property:

property this_item : missing value

script the_script
	tell application "TextEdit"
		activate
		open this_item
	end tell
	tell application "System Events" to tell process "TextEdit"
		set frontmost to true
		tell window 1
			tell scroll area 1 to if exists scroll bar 2 then keystroke "w" using {command down, shift down} -- if the window opens in "Wrap to Page" mode, change to "Wrap to Text"			
			click (first button whose subrole is "AXZoomButton") -- start with a fully zoomed window
			set {x_max, y_max} to size -- get the starting size of the fully zoomed window
			repeat with xy_axis in {"y", "x"} -- resize the vertical then the horizontal dimension of the window
				if contents of xy_axis is "x" then -- set the maximum and minimum amounts (delta) by which the window will be resized with each iteration, as well as switches to apply the resizing to the horizontal or vertical dimension selectively
					set {delta_max, delta_min, x_delta_switch, y_delta_switch} to {-x_max / 2, 10, 1, 0}
				else if contents of xy_axis is "y" then
					set {delta_max, delta_min, x_delta_switch, y_delta_switch} to {-y_max / 2, 10, 0, 1}
				end if
				set delta_value to delta_max -- set the starting delta to 1/2 the dimension of the fully zoomed window
				repeat
					set abs_delta_value to delta_value * (1 - 2 * ((delta_value < 0) as integer)) -- absolute value of delta
					tell scroll area 1 to tell scroll bar 1 to if enabled then -- if scroll bar is enabled, window size is too small
						if delta_value = delta_max then exit repeat -- quit if fully zoomed window is too small
						if delta_value < 0 then set delta_value to delta_value * -1 -- otherwise, be sure the next window resizing is in the optimal direction
					else -- if scroll bar is not enabled, window size is either optimal or too big
						if abs_delta_value < delta_min then exit repeat -- quit if window size is optimal
						if delta_value > 0 then set delta_value to delta_value * -1 -- otherwise, be sure the next window resizing is in the optimal direction
					end if
					set {x, y} to size
					set size to {x + delta_value * x_delta_switch, y + delta_value * y_delta_switch} -- resize the window
					if abs_delta_value > delta_min then set delta_value to delta_value / 2 -- halve the amount by which the window will next be resized, but no smaller than the minimum amount
				end repeat
			end repeat
			set {x, y} to size
			set position to {round (x_max - x) / 2 rounding down, round (y_max - y) / 2 rounding down} -- center window on screen
		end tell
	end tell
end script

on open these_items
	repeat with i from 1 to the count of these_items
		set this_item to (item i of these_items)
		run script the_script
	end repeat
	tell application "Finder"
		activate
		display dialog ("Finished!") buttons " " giving up after 1
	end tell
end open

(Apologies for the editing error. The timings are rounded to the nearest thousandth of a second instead of to the nearest hundredth as I’d claimed.)

I’m not surprised by the results myself. I don’t pretend to know exactly what’s going on, but the implication seems to be that communication between OSAXen and applications is much faster than communication between applications “ and that little communication at all is even faster than that.

Theory:

‘run script’ is a StandardAdditions command. An applet can call it quickly to run a script object or script text. ‘run script’ itself then executes the passed script, communicating quickly with any targeted applications. When the script has been fully executed, ‘run script’ reports this back to the applet along with any result. Apart from calling ‘run script’, the applet itself has no part in the execution of the passed script.

‘ignoring application responses’ doesn’t speed up inter-application communication. It just causes one side of the conversation to be ignored. In one direction, the applet (or even ‘run script’) motor-mouths commands at the targeted application(s) without waiting for or listening to the replies. In the other direction, targeted applications find more commands already waiting when they finish carrying out the previous ones. This allows both the applet and the targeted applications get through their lists of things to do more quickly. But it’s a “government target” kind of efficiency. There’s no pause for thought and the left hand doesn’t know what the right is doing.

Nigel,

Well, I am surprised. You made me realise there might a tradeoff here. OSAXen certainly take some more time and overhead to get activated, but supposedly make better claims on the Unix Task manager from then - and still win.
I thought it be interesting to see in OS9 (on my youngest son’s 1995 iMac) where these effects don’t count, and indeed - here bmose’s Finder “get name” routine (500 repeats) gets executed in:
run script: 8 seconds,
run: 7 seconds,
vanilla: 6 seconds !
So the OSAX’en advantage seems to have come in with Unix…

I don’t think this is a proper way to describe what happens.
‘ignoring application responses’ DOES speed up inter-application communication & execution.
The “lists of things creation” is right, but execution either waits for the other apps responses or not.
My experiments do show 100+% differences in results!

Hi, Eelco.

I don’t think I can advance this discussion any further, since I don’t know for sure what goes on “under the bonnet”. The AppleScript Language Guide says this about the ‘application responses’ attribute:

. which is what I said. Here’s a fun demo:

ignoring application responses
	tell application "Finder"
		get entire contents of home
		beep
	end tell
end ignoring

beep 2
--> Two beeps, followed some time later by one.

But I think the relevant point for this thread is that using ‘run script’ to run script code both speeds up a script applet’s interaction with a scripted application and returns results to the script. Use ‘ignoring application responses’ only when you know it’s suitable.