1

I wanted to understand the max stack frames / size in better detail.

function computeMaxCallStackFrames() {
    try {
        //
        // <Variable part here>
        //
        return 1 + computeMaxCallStackFrames();
    } catch (e) {
        // Call stack overflow
        return 1;
    }
}
computeMaxCallStackFrames()  // 5447  on Chrome 58.0.3029.110, Mac 10.11

Changing // <Variable part here> to:

Test case 1:

var a = 1;
// computeMaxCallStackFrames() => 5220

Test case 2:

var a = 1;
var b = 2;    
// computeMaxCallStackFrames() => 5011

Test case 3:

var a = 1;
var b = {};    
// computeMaxCallStackFrames() => 5011  (no change from `var b = 2;`)

Test case 4:

var a = 1;
var b = {c: 3};    
// computeMaxCallStackFrames() => 5010

My question is why Test case 4 changes at all and why it changes by 1 fewer stack frames.

** edit ** any relevant links to v8 source would be fantastic. Even just showing where stack size memory limit is set would be great intro. Apologies for not asking this by the time Andreas Rossberg and jmrk already left their great answer and comments respectively.

AJP
  • 26,547
  • 23
  • 88
  • 127

1 Answers1

2

There are many, many factors that can influence the size of an individual stack frame in V8 and other JS engines: e.g., the number of arguments, the number of variables, the life time of these variables, how these variables are used, whether they are captured in an inner closure, whether there are try-handlers, what other operations occur in the function (which may require temporary variables internally), and even the type of the actual arguments that have been passed to the function before. Moreover, it all depends on runtime profiling and the optimisation level of the function (and other functions it may call), which may change over time, and even on when it was compiled/optimised/deoptimised, which can be somewhat non-deterministic.

That is all to say that -- as always -- micro benchmarks will not give useful information, and at worst be wildly misleading. You really need to measure the concrete program you care about.

Andreas Rossberg
  • 34,518
  • 3
  • 61
  • 72
  • Understood, was hoping someone might say all of that ;) Was also hoping someone might follow up with a link to the v8 source code showing some comment or test case where empty unmutated objects are detected and made to reference some existing empty object where as new non empty objects get put onto the heap and then subsequent identical, unmutated, unaccessed objects just reference that... big ask I know. – AJP Jul 02 '17 at 11:31
  • 1
    +1 to this answer. To add a small data point: if you use `--print-bytecode`, you can see that the function's frame size is the same in cases 2, 3, 4 (I see "48" with a recent build; with a different version the value might differ). So it's not `computeMaxCallStackFrames` itself, but something else somewhere in the system that's using a tiny bit more stack space when the object literal is non-empty. – jmrk Jul 02 '17 at 11:33
  • 1
    @AJP's comment: if you want to ask another question, please ask another question. The short answer is that object literal handling is much more complicated than what you suggest. And there's no "deduplication" of objects (empty/unmodified or otherwise), because identity is observable: `var a = {}; var b = {}; a === b /* -> false */` – jmrk Jul 02 '17 at 11:37
  • Thanks jmrk. `identity is observable`, yes understood. Instead of "unmutated, unaccessed objects" my comment should have read "unmutated, unobserved objects" though I appreciate this may be impossible and it sounds like it's not one of the optimisations / behaviours occurring here. – AJP Jul 02 '17 at 11:50