New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.

Issue 1065 link

Starred by 24 users

Issue metadata

Status: Archived
Owner:
Last visit > 30 days ago
Closed: Dec 2017
HW: ----
NextAction: ----
OS: ----
Priority: 2
Type: FeatureRequest



Sign in to add a comment

try catch incurs an unexpected performance penalty

Project Member Reported by vegorov@chromium.org, Jan 20 2011

Issue description

attached test has three different test cases:

  measure("baseline", function (j) {
      var s = 0;
      for (var i = 0; i < j; i++) s = i;
      for (var i = 0; i < j; i++) s = i;
      for (var i = 0; i < j; i++) s = i;
      for (var i = 0; i < j; i++) s = i;
      return s;
    }, 1000000);

  measure("try-catch #1", function (j) {
      var s = 0;
      try {
        for (var i = 0; i < j; i++) s = i;
        for (var i = 0; i < j; i++) s = i;
        for (var i = 0; i < j; i++) s = i;
        for (var i = 0; i < j; i++) s = i;
      } catch (e) {
      }
      return s;
    }, 1000000);

  measure("try-catch #2", function (j) {
      try {
      } catch (e) {
      }

      var j_ = j;
      var s = 0;
      for (var i = 0; i < j_; i++) s = i;
      for (var i = 0; i < j_; i++) s = i;
      for (var i = 0; i < j_; i++) s = i;
      for (var i = 0; i < j_; i++) s = i;
      return s;
  }, 1000000);

Running this test cases on V8 bleeding_edge HEAD with --noopt gives an unexpected result:

baseline: 11.3 ms.
try-catch #1: 30.6 ms.
try-catch #2: 22.4 ms.


 
try-catch.js
1.2 KB View Download
Labels: -Type-Bug Type-FeatureRequest
Status: Assigned
The performance difference between try-catch #1 and try-catch #2 is because parameter access is slower than local variable access for functions containing with or try/catch, even for code outside the with or try/catch.

SVN revision 6426 on bleeding_edge removes the difference (by speeding up parameters, not slowing down locals), so those two are on par with each other.

There is still a difference from the baseline.  Because of the with or try/catch we allocate all the parameters and locals in the heap.  The code to read from such a variable is something like:

0xf54c3dc8   424  8b460b         mov eax,[esi+0xb]
0xf54c3dcb   427  8b401f         mov eax,[eax+0x1f]

as opposed to the code to read from a stack-allocated variable:

0xf54c3aa5    69  8b45f4         mov eax,[ebp+0xf4]

The extra read is to go from the current context (which may be a with context) to the current function's context.  In this case it's a nop because they're the same and I think we can statically eliminate it (I'll try).

The code to store to such a variable is, even for the case of a small integer:

0xf54c3dce   430  8b4e0b         mov ecx,[esi+0xb]
0xf54c3dd1   433  894123         mov [ecx+0x23],eax
0xf54c3dd4   436  89c2           mov edx,eax
0xf54c3dd6   438  f6c201         test_b edx,0x1
0xf54c3dd9   441  7424           jz 479  (0xf54c3dff)
:
:
0xf54c3dff   479  ....

as opposed to:

0xf54c3aa8    72  8945f0         mov [ebp+0xf0],eax

where the extra code in addition to the extra indirection is the write barrier for heap writes.  That's unfortunately a bit harder to avoid.

We should avoid treating try/catch as badly as with, but I'm not going to get to that immediately.  We should also be able to use the optimizing compiler for code containing try/catch.

In the mean time, a performance recommendation is (a) remember that using 'with' is slow, (b) for all code in the function, not just in the body of the with, and (c) that try/catch is like with in this regard, (d) so performance critical code should in such a function should be moved into a helper function.  The following code is on par with the baseline:


  function body(j_, s) {
    for (var i = 0; i < j_; i++) s = i;
    for (var i = 0; i < j_; i++) s = i;
    for (var i = 0; i < j_; i++) s = i;
    for (var i = 0; i < j_; i++) s = i;
    return s;
  }

  measure("try-catch #3", function try_catch_3 (j) {
      try {
      } catch (e) {
      }

      return body(j, 0);
  }, 1000000);

I've been reading about this, and a lot of sources mention that variables are allocated on the heap rather than the stack, which is the source of the performance problem. My question is: *why* is this done? Why not unwind the stack like other langauges do?

It looks like even simply wrapping the try block's code in a function call removes almost all the performance hit. Why can't the principles at play there be exploited to speed this stuff up?
V8 does unwind the stack (can you think of any other way to implement throw/catch?).  The real performance problem is that functions containing try/catch are not optimized.

* I think catch-bound variables are allocated in the heap mainly for historical reasons.  They originally used the same implementation machinery as 'with'.  This was changed in 2011.  But when it changed, V8 kept the heap allocation as part of the old implementation.  I can't think of a super compelling reason not to stack allocate them, other than the implementation effort and the relatively low priority.  They do take up extra space in the stack frame and they do require initialization with some value that is safe for GC.

* You should definitely profile before you conclude this is a performance problem.  For reads, the code contains only an extra register-register move in most cases.  You'd have to be reading the catch variable in a loop to even observe the difference.  For heap writes, there is a write barrier for the garbage collector.  But who writes to a catch-bound variable anyway?  If you're doing that in a loop there is a simple workaround --- don't.

* V8 optimizes at the granularity of a whole function.  It is not simple to have a function which is partly optimized and partly not (with a stack frame that is a mix of optimized/unoptimized) --- that's the principle at play when a programmer abstracts out part of a body into a separate function.  I would estimate that supporting such behavior in the optimizing compiler would be more difficult than just supporting try/catch in the first place.
Labels: Priority-2

Comment 5 by adamk@chromium.org, Dec 19 2017

Status: Archived (was: Assigned)
Archiving some old issues with no apparent applicability to the present.

Sign in to add a comment