19

I never knew use strict to speed up runtime, however a simple use strict is making my benchmark substantially faster, and the slower one grossly slower (over twice as slow). What's going on?

//
// RUN WITH AND WITHOUT THIS
//
"use strict";

var assert = require('assert');

var slice = [].slice;

function thunkify_fast(fn){
  assert('function' == typeof fn, 'function required');

  return function(){
    var args = new Array(arguments.length);
    for(var i = 0; i < args.length; ++i) {
      args[i] = arguments[i];
    }
    var ctx = this;

    return function(done){
      var called;

      args.push(function(){
        if (called) return;
        called = true;
        done.apply(null, arguments);
      });

      try {
        fn.apply(ctx, args);
      } catch (err) {
        done(err);
      }
    }
  }
};

function thunkify_slow(fn){
  assert('function' == typeof fn, 'function required');

  return function(){
    var args = slice.call(arguments);
    var ctx = this;

    return function(done){
      var called;

      args.push(function(){
        if (called) return;
        called = true;
        done.apply(null, arguments);
      });

      try {
        fn.apply(ctx, args);
      } catch (err) {
        done(err);
      }
    }
  }
};


var fn = function () { };

var Benchmark = require('benchmark');
var suite = new Benchmark.Suite;


//
// Only one wrapper can be sent through the optimized compiler
//
suite.add( 'thunkify#fast', function () { thunkify_fast(fn)(function(){}) } )
    .add( 'thunkify#slow', function () { thunkify_slow(fn)(function(){}) } )
    .on('cycle', function(event) { console.log(String(event.target)); })
    .on('complete', function() {
        console.log('Fastest is ' + this.filter('fastest').pluck('name'));
    })
    .run();

Without that top "use strict", the results are inline with this,

$ node --allow-natives-syntax test.js 
thunkify#fast x 8,511,605 ops/sec ±1.22% (95 runs sampled)
thunkify#slow x 4,579,633 ops/sec ±0.68% (96 runs sampled)
Fastest is thunkify#fast

However, with that "use strict;", I'm getting this,

$ node --allow-natives-syntax test.js 
thunkify#fast x 9,372,375 ops/sec ±0.45% (100 runs sampled)
thunkify#slow x 1,483,664 ops/sec ±0.93% (96 runs sampled)
Fastest is thunkify#fast

I'm running nodejs v0.11.13. This is all part of the work I'm doing to speed up node-thunkify using this guide. Interestingly the bluebird optimization guide does not mention use strict;'s beneficial performance.

Messing around with it more, if I change the test case to be,

var f_fast = thunkify_fast(fn);
var f_slow = thunkify_slow(fn);
suite.add( 'thunkify#fast', function () { f_fast(function(){}) } )
  .add( 'thunkify#slow', function () { f_slow(function(){}) } )
  .on('cycle', function(event) { console.log(String(event.target)); })
  .on('complete', function() {
    console.log('Fastest is ' + this.filter('fastest').pluck('name'));
  })
  .run();

thereby removing the calls thunkify I still see the same thing.. The case with use strict is slower on unoptimized code, and faster on optimized code,

No strict

thunkify#fast x 18,910,556 ops/sec ±0.61% (100 runs sampled)
thunkify#slow x 5,148,036 ops/sec ±0.40% (100 runs sampled)

"use strict;"

thunkify#fast x 19,485,652 ops/sec ±1.27% (99 runs sampled)
thunkify#slow x 1,608,235 ops/sec ±3.37% (93 runs sampled)
Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
  • Have you looked at this? Specifically how “use strict” does the following “It disables features that are confusing or poorly thought out.” http://stackoverflow.com/questions/1335851/what-does-use-strict-do-in-javascript-and-what-is-the-reasoning-behind-it – Giacomo1968 Jun 02 '14 at 14:55
  • 3
    Why would disabling those features make runtime massively *slower* on unoptimized functions, and >10% faster on optimized functions? – Evan Carroll Jun 02 '14 at 14:57
  • 1
    The only conclusion I can come to is that strict must somehow slowdown modifications to `arguments`, and speed up simply iterations and element assignments... – Evan Carroll Jun 02 '14 at 15:18

3 Answers3

14

The reason for this slowness is in this check inside ArraySlice builtin. It tests whether we are trying to slice arguments object and if we do, then uses a fast code to do that. However it only checks for sloppy mode arguments object. When you allocate arguments object inside the strict function you get strict mode arguments object made from native_context()->strict_arguments_boilerplate() which means the check above fails to recognize it and falls through to the generic JavaScript code which is slower than specialized hand-coded C++ fast path it would take for a sloppy arguments object.

Vyacheslav Egorov
  • 10,302
  • 2
  • 43
  • 45
11

Following is a quote from Mozilla's article on JavaScript Strict Mode

JavaScript's flexibility makes it effectively impossible to do this without many runtime checks. Certain language functions are so pervasive that performing runtime checks has considerable performance cost. A few strict mode tweaks, plus requiring that user-submitted JavaScript be strict mode code and that it be invoked in a certain manner, substantially reduce the need for those runtime checks.

The above quote makes it quite clear that there are certain performance improvements while using strict mode.

As you know, strict mode disables several features which JavaScript provided previously(most of them considered bad practice). Since the browser can easily throw errors when using strict mode, it doesn't have to perform checks and assume code corrections for you. Thus, leading to performance improvements.

chaudharyp
  • 3,394
  • 3
  • 26
  • 42
Chetan Bhasin
  • 3,503
  • 1
  • 23
  • 36
9

Just wanted to add to Chetan's answer. I will point you to a question you had asked little while ago. Most likely this has the answer.

Strict mode makes certain assumptions about your code and adds extra checks. These checks have two effects related to performance (when compared to normal mode) :

  1. take extra CPU time to check
  2. helps compiler understand code and optimize it better.

The performance will improve in strict mode so long as 2 outweighs 1. This works as expected for your first function. For your your second function no optimizations can be made !! The compiler bails out when it sees unsafe argument usage. So the first effect is all that remains.

I guess non-optimizable code takes lot more penalty in strict code. The extra checks yield nothing.

Community
  • 1
  • 1
user568109
  • 47,225
  • 17
  • 99
  • 123