[quickjs-devel] Re: C stack usage

  • From: Saúl Ibarra Corretgé <s@xxxxxxxxxx>
  • To: quickjs-devel@xxxxxxxxxxxxx
  • Date: Fri, 23 Aug 2019 09:08:17 +0200

On 21/08/2019 17:44, Simon CORSIN wrote:

Hey folks,

I have been experimenting with QuickJS and so far it's been great. I had
some questions regarding the C stack frame usage. It seems that each
call to /JS_CallInternal/ uses about 10K of C stack in debug when
running in 64bits on my machine, which hinders how many JS calls we can
make before getting a stack overflow. Compiling and running with
optimization does help significantly, but we are still far from what
Node (V8) or Safari (JavaScript) can achieve.

Consider this example:

(function() {
function countRecursion(obj) {
obj.total += 1;

countRecursion(obj);
}

const obj = {
total: 0
};

try {
countRecursion(obj);
} catch (err) {}

console.log('Total recursion: ', obj.total);
})();


If I compile qjs in debug mode and run it, I get /27/ recursions before
getting a stack overflow. In release mode I get /259/ (almost 10x
improvement). For comparison, in Node I get /13966/ recursions, in
Safari /13933/, in Duktape /9998/.
I understand of course that those results will depend on the max stack
size value configured in each of those engines. QuickJS has a default of
256KB, which seems like a reasonable default amount. For reference on
iOS, the default max stack size for secondary threads is 512KB, so the
fact that QuickJS is set to half of that seems fine to me.

I've been toying around trying to find a way to make /JS_CallInternal/
use less C stack. According
to 
http://events17.linuxfoundation.org/sites/events/files/slides/GCC%252FClang%20Optimizations%20for%20Embedded%20Linux.pdf
 ,
GCC has a /-fconserve-stack/ option, however it doesn't seem to exist on
clang at least not on the version I have on my machine. I haven't found
any compiler options that helped in raising the number I get when I run
my recursion benchmark. I then tried to remove the inline functions
within quickjs itself and replaced them by just regular function calls,
and got those results:

/Before removing inlines:
/
./qjs recursion.js
Total recursion:  259
./qjs-debug recursion.js
Total recursion:  27

/After removing inlines:
/./qjs recursion.js
Total recursion:  292
./qjs-debug recursion.js
Total recursion:  35

Removing inline function calls did help raising the JS call limit a bit,
maybe at the cost of reduced performance (I didn't check the real impact
on performance though). I then tried to reduce the number of local
variables used, by re-using local variables instead of creating new
ones. For example, instead of using /JSValue obj1/ it uses a shared
/JSValue tmp/ used by all bytecode operations that requires to store a
/JSValue/ on the stack. I didn't go too deep into this, just wanted to
see if it could help and this is the result I got:

./qjs recursion.js
Total recursion:  297
./qjs-debug recursion.js
Total recursion:  39

I was actually surprised to see that it helped at all. I'd expect the
compiler would be able to optimize that out fairly easily.

That being explained, these are my questions:
- What do you think would be the best way to significantly improve the
JS call limit?
- Can this be done without having to move the JS stack from the C stack
into the heap?
- If you can think of some low hanging fruits, can you give me any
pointers of what would need to be done? I'd be happy to help if needed.

Thanks again for the awesome work!

Cheers,
Simon

  

I just did an experiment by calling JS_SetMaxStackSize with 1024 * 1024
and now I get 1090, up from 271 on the default stack size, which seems
to be 256 * 1024.


Cheers,

-- 
Saúl

Other related posts: