Open scripting opened 3 years ago
Here's what's generated from the script that figures out how many characters are in the Scripting News RSS file.
(async function () {const urlFeed = 'http://scripting.com/rss.xml';
const feedText = await http.readUrl(urlFeed);
await dialog.alert('There are ' + feedText.length + ' chars in the Scripting News feed.');})()
What browser or JS engine are you using for debugging. Running the Chrome dev tools (via Brave) I don't don't see a console message like the one in step 4 of your issue.
However, by inserting a debugger statement into a script I can see the generated code that runs for a script. For example, the script:
function f () {debugger; return 3+4} f();
turns into:
(async function () {async function f() {
debugger;
return 3 + 4;
}
await f();})()
Turning every function and functional into an async call is deadly from a perf perspective because each such call takes multiple trips through the engines event loop, even if nothing async is actually happening.
That probably explains why a script I wrote to download my twitter mentions into an outline and then light process them is taking about 10 seconds to run. That includes 3 seconds to run twitter.getMentions.timeline and over 6.5 seconds to make an outline out of what was returned. The 3 seconds is perhaps not unreasonable because it is reading 200 tweets across the net. But my intuition is that in memory processing of 200 tweets creating an outline is something that should take less than a second. I suspect it is the event loop delays for the async calls to the op verbs that is taking all the time.
Here is that translated script:
async function () {debugger;
var t0 = await Date.now();
const tweets = await twitter.getMentionsTimeline();
var t1 = await Date.now();
for (tweet of await twitter.getMentionsTimeline()) {
await op.insert(`${ tweet.when } @${ tweet.screenname }`, down);
await op.attributes.addGroup(tweet);
await op.insert(tweet.text, right);
await op.insert(`<a link="${ tweet.link }">${ tweet.link }</a>`, down);
await op.go(left, 1);
}
await op.firstSummit();
await dialog.alert(`t1-t0 ${ t1 - t0 } t2-t1 ${ await Date.now() - t1 } `);})()
I think we're going to have to figure out a way do avoid making async calls (ie, using await) to things that really don't need to be async.
Allen, let's do real benchmarking before coming to any conclusions, and avoid terms like "deadly" -- some costs are worth bearing, and there could be other explanations for the slowness of your twitter scripts which i have seen too, and they did not involve any processing at all, just moving data from twitter, to the server to the client. Sometimes the server does weird things, haven't had a chance to look into that yet.
With that caution, of course I want the scripts to run as fast as possible.
I did some more basic call performance measurements using there two functions:
async function timeSyncCall(f, iterations=1) {
let i = iterations;
let start = performance.now(); //aka, window.performance.now, part of the browser performance API
while (i-- > 0) f();
let end = performance.now();
return {iterations, total: end-start, perIteration: (end-start)/iterations, f: f.toString()};
}
async function timeAsyncCall(f, iterations=1) {
let i = iterations;
let start = performance.now(); //aka, window.performance.now, part of the browser performance API
while (i-- > 0) await f();
let end = performance.now();
return {iterations, total: end-start, perIteration: (end-start)/iterations, f: f.toString()};
}
Note that the functions are identical except that timeAsyncCall does an await
when calling the function under test.
Here are some raw results:
await timeSyncCall( function() {return;}, 10_000_000)
> {iterations: 10000000, total: 90.90000009536743, perIteration: 0.000009090000009536743, f: "function() {return;}"}
await timeSyncCall( async function() {return;}, 10_000_000)
> {iterations: 10000000, total: 3479.7000000476837, perIteration: 0.0003479700000047684, f: "async function() {return;}"}
await timeAsyncCall( function() {return;}, 10_000_000)
> {iterations: 10000000, total: 9075.200000047684, perIteration: 0.0009075200000047684, f: "function() {return;}"}
await timeAsyncCall( async function() {return;}, 10_000_000)
> {iterations: 10000000, total: 10809.099999904633, perIteration: 0.0010809099999904632, f: "async function() {return;}"}
Translating into more human terms: A normal call/return to a normal function takes about 9 nanoseconds A normal call/return to an async function takes about 349 nanoseconds A async call/return to a normal function takes about 908 nanoseconds A async call/return to an async function takes about 1081 nanoseconds
Note that a normal call to an async function returns a promise created by the function and that an await
call to a normal function waits on a promise created after the function returns.
I get it, but what matters is this --
Is the performance difference on the kinds of things we do from scripts significant, i.e. is the difference perceivable by a human being? The difference between 9 and 1081 nanoseconds is a big deal in some contexts, but in terms of me waiting for a script to complete, there would have to be a lot of those for it to add up to something measurable.
Then the question is how to define "things we do from scripts." That's going to take some thinking to come up with reasonable benchmarks for that.
Thanks for digging into this. :smile:
@allenwb -- I wanted to give you an idea of the "kinds of things scripts do."
Here's the source for a workhorse script I wrote years ago and have been adding to over time. This does all the builds of all my code from outlines I edit in the OPML Editor (a distribution of Frontier). It's my main devops script, not the only one, I have manu.
http://scripting.com/publicfolder/drummer/examples/nodeEditorSuite.uploadScripts.opml
The language is UserTalk, not JavaScript. But it should be reasonably readable.
I was chatting with @allenwb this afternoon and he asked for a way to see the script code that Drummer is generating when you run a script from the Scripts menu or with Cmd-/.
Here's how.
Open the JavaScript console.
Run a script.
You should see a line that says something like this:
runScriptText: processedScriptText == (async function () {await dialog.alert('Hello World!');})()
That's the generated script code.
Let's have fun!