Skip to content

Instantly share code, notes, and snippets.

@saabi
Created May 10, 2012 13:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save saabi/2653077 to your computer and use it in GitHub Desktop.
Save saabi/2653077 to your computer and use it in GitHub Desktop.
Simple optimization of a compiled Jade template
self =
{
header: "Header",
header2: "Header2",
header3: "Header3",
header4: "Header4",
header5: "Header5",
header6: "Header6",
list: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']
};
attrs = function (obj, escaped) {
var buf = []
, terse = obj.terse;
delete obj.terse;
var keys = Object.keys(obj)
, len = keys.length;
if (len) {
buf.push('');
for (var i = 0; i < len; ++i) {
var key = keys[i]
, val = obj[key];
if ('boolean' == typeof val || null == val) {
if (val) {
terse
? buf.push(key)
: buf.push(key + '="' + key + '"');
}
} else if (0 == key.indexOf('data') && 'string' != typeof val) {
buf.push(key + "='" + JSON.stringify(val) + "'");
} else if ('class' == key && Array.isArray(val)) {
buf.push(key + '="' + exports.escape(val.join(' ')) + '"');
} else if (escaped[key]) {
buf.push(key + '="' + exports.escape(val) + '"');
} else {
buf.push(key + '="' + val + '"');
}
}
}
return buf.join(' ');
};
attrs2 = function (obj, escaped) {
var terse = obj.terse;
delete obj.terse;
var keys = Object.keys(obj)
, len = keys.length;
var s = ' ';
if (len) {
for (var i = 0; i < len; ++i) {
var key = keys[i]
, val = obj[key];
if ('boolean' == typeof val || null == val) {
if (val) {
terse
? (s+=key)
: (s+=key + '="' + key + '" ');
}
} else if (0 == key.indexOf('data') && 'string' != typeof val) {
s += key + "='" + JSON.stringify(val) + "' ";
} else if ('class' == key && Array.isArray(val)) {
s += key + '="' + exports.escape(val.join(' ')) + '" ';
} else if (escaped[key]) {
s += key + '="' + exports.escape(val) + '" ';
} else {
s += key + '="' + val + '" ';
}
}
}
return s;
};
jt1 = function ()
{
//var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;
var buf = [];
//var self = locals || {};
var interp;
buf.push('<div><h1');
buf.push(attrs({ "class": ('header') }, {}));
buf.push('>');
var __val__ = self.header
buf.push(null == __val__ ? "" : __val__);
buf.push('</h1><h2');
buf.push(attrs({ "class": ('header2') }, {}));
buf.push('>');
var __val__ = self.header2
buf.push(null == __val__ ? "" : __val__);
buf.push('</h2><h3');
buf.push(attrs({ "class": ('header3') }, {}));
buf.push('>');
var __val__ = self.header3
buf.push(null == __val__ ? "" : __val__);
buf.push('</h3><h4');
buf.push(attrs({ "class": ('header4') }, {}));
buf.push('>');
var __val__ = self.header4
buf.push(null == __val__ ? "" : __val__);
buf.push('</h4><h5');
buf.push(attrs({ "class": ('header5') }, {}));
buf.push('>');
var __val__ = self.header5
buf.push(null == __val__ ? "" : __val__);
buf.push('</h5><h6');
buf.push(attrs({ "class": ('header6') }, {}));
buf.push('>');
var __val__ = self.header6
buf.push(null == __val__ ? "" : __val__);
buf.push('</h6><ul');
buf.push(attrs({ "class": ('list') }, {}));
buf.push('>');
// iterate self.list
(function()
{
if ('number' == typeof self.list.length)
{
for (var $index = 0, $$l = self.list.length; $index < $$l; $index++)
{
var item = self.list[$index];
buf.push('<li');
buf.push(attrs({ "class": ('item') }, {}));
buf.push('>');
var __val__ = item
buf.push(null == __val__ ? "" : __val__);
buf.push('</li>');
}
}
else
{
for (var $index in self.list)
{
if (self.list.hasOwnProperty($index))
{
var item = self.list[$index];
buf.push('<li');
buf.push(attrs({ "class": ('item') }, {}));
buf.push('>');
var __val__ = item
buf.push(null == __val__ ? "" : __val__);
buf.push('</li>');
}
}
}
}).call(this);
buf.push('</ul></div>');return new Buffer(buf.join(""));
}
jt2 = function ()
{
//var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;
//var self = locals || {};
s ='<div><h1' + attrs2({ "class": ('header') }, {}) + '>';
var __val__ = self.header;
s += (null == __val__ ? "" : __val__) + '</h1><h2' + attrs2({ "class": ('header2') }, {}) + '>';
var __val__ = self.header2;
s += (null == __val__ ? "" : __val__) + '</h2><h3' + attrs2({ "class": ('header3') }, {}) + '>';
var __val__ = self.header3;
s += (null == __val__ ? "" : __val__) + '</h3><h4' + attrs2({ "class": ('header4') }, {}) + '>';
var __val__ = self.header4;
s += (null == __val__ ? "" : __val__) + '</h4><h5' + attrs2({ "class": ('header5') }, {}) + '>';
var __val__ = self.header5;
s += (null == __val__ ? "" : __val__) + '</h5><h6' + attrs2({ "class": ('header6') }, {}) + '>';
var __val__ = self.header6;
s += (null == __val__ ? "" : __val__) + '</h6><ul' + attrs2({ "class": ('list') }, {}) + '>';
// iterate self.list
(function()
{
if ('number' == typeof self.list.length)
{
for (var $index = 0, $$l = self.list.length; $index < $$l; $index++)
{
var item = self.list[$index];
s += '<li' + attrs2({ "class": ('item') }, {}) + '>';
var __val__ = item;
s += (null == __val__ ? "" : __val__) + '</li>';
}
}
else
{
for (var $index in self.list)
{
if (self.list.hasOwnProperty($index))
{
var item = self.list[$index];
s += '<li' + attrs2({ "class": ('item') }, {}) + '>';
var __val__ = item;
s += (null == __val__ ? "" : __val__) + '</li>';
}
}
}
}).call(this);
s += '</ul></div>';
return new Buffer(s);
}
jt3 = function ()
{
//var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;
//var self = locals || {};
s ='<div><h1' + attrs2({ "class": ('header') }, {}) + '>';
var __val__ = self.header;
s += (null == __val__ ? "" : __val__);
s += '</h1><h2';
s += attrs2({ "class": ('header2') }, {});
s += '>';
var __val__ = self.header2;
s += (null == __val__ ? "" : __val__);
s += '</h2><h3';
s += attrs2({ "class": ('header3') }, {});
s += '>';
var __val__ = self.header3;
s += (null == __val__ ? "" : __val__);
s += '</h3><h4';
s += attrs2({ "class": ('header4') }, {});
s += '>';
var __val__ = self.header4;
s += (null == __val__ ? "" : __val__);
s += '</h4><h5';
s += attrs2({ "class": ('header5') }, {});
s += '>';
var __val__ = self.header5;
s += (null == __val__ ? "" : __val__);
s += '</h5><h6';
s += attrs2({ "class": ('header6') }, {});
s += '>';
var __val__ = self.header6;
s += (null == __val__ ? "" : __val__);
s += '</h6><ul';
s += attrs2({ "class": ('list') }, {});
s += '>';
// iterate self.list
(function()
{
if ('number' == typeof self.list.length)
{
for (var $index = 0, $$l = self.list.length; $index < $$l; $index++)
{
var item = self.list[$index];
s += '<li';
s += attrs2({ "class": ('item') }, {});
s += '>';
var __val__ = item;
s += (null == __val__ ? "" : __val__);
s += '</li>';
}
}
else
{
for (var $index in self.list)
{
if (self.list.hasOwnProperty($index))
{
var item = self.list[$index];
s += '<li';
s += attrs2({ "class": ('item') }, {});
s += '>';
var __val__ = item;
s += (null == __val__ ? "" : __val__);
s += '</li>';
}
}
}
}).call(this);
s += '</ul></div>';
return new Buffer(s);
}
jt4 = function ()
{
//var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;
//var self = locals || {};
s ='<div><h1' + attrs({ "class": ('header') }, {}) + '>';
var __val__ = self.header;
s += (null == __val__ ? "" : __val__) + '</h1><h2' + ({ "class": ('header2') }, {}) + '>';
var __val__ = self.header2;
s += (null == __val__ ? "" : __val__) + '</h2><h3' + attrs({ "class": ('header3') }, {}) + '>';
var __val__ = self.header3;
s += (null == __val__ ? "" : __val__) + '</h3><h4' + attrs({ "class": ('header4') }, {}) + '>';
var __val__ = self.header4;
s += (null == __val__ ? "" : __val__) + '</h4><h5' + attrs({ "class": ('header5') }, {}) + '>';
var __val__ = self.header5;
s += (null == __val__ ? "" : __val__) + '</h5><h6' + attrs({ "class": ('header6') }, {}) + '>';
var __val__ = self.header6;
s += (null == __val__ ? "" : __val__) + '</h6><ul' + attrs({ "class": ('list') }, {}) + '>';
// iterate self.list
(function()
{
if ('number' == typeof self.list.length)
{
for (var $index = 0, $$l = self.list.length; $index < $$l; $index++)
{
var item = self.list[$index];
s += '<li' + attrs({ "class": ('item') }, {}) + '>';
var __val__ = item;
s += (null == __val__ ? "" : __val__) + '</li>';
}
}
else
{
for (var $index in self.list)
{
if (self.list.hasOwnProperty($index))
{
var item = self.list[$index];
s += '<li' + attrs({ "class": ('item') }, {}) + '>';
var __val__ = item;
s += (null == __val__ ? "" : __val__) + '</li>';
}
}
}
}).call(this);
s += '</ul></div>';
return new Buffer(s);
}
jt5 = function ()
{
//var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;
var buf = [];
//var self = locals || {};
var interp;
buf.push('<div><h1');
buf.push(attrs2({ "class": ('header') }, {}));
buf.push('>');
var __val__ = self.header
buf.push(null == __val__ ? "" : __val__);
buf.push('</h1><h2');
buf.push(attrs2({ "class": ('header2') }, {}));
buf.push('>');
var __val__ = self.header2
buf.push(null == __val__ ? "" : __val__);
buf.push('</h2><h3');
buf.push(attrs2({ "class": ('header3') }, {}));
buf.push('>');
var __val__ = self.header3
buf.push(null == __val__ ? "" : __val__);
buf.push('</h3><h4');
buf.push(attrs2({ "class": ('header4') }, {}));
buf.push('>');
var __val__ = self.header4
buf.push(null == __val__ ? "" : __val__);
buf.push('</h4><h5');
buf.push(attrs2({ "class": ('header5') }, {}));
buf.push('>');
var __val__ = self.header5
buf.push(null == __val__ ? "" : __val__);
buf.push('</h5><h6');
buf.push(attrs2({ "class": ('header6') }, {}));
buf.push('>');
var __val__ = self.header6
buf.push(null == __val__ ? "" : __val__);
buf.push('</h6><ul');
buf.push(attrs2({ "class": ('list') }, {}));
buf.push('>');
// iterate self.list
(function()
{
if ('number' == typeof self.list.length)
{
for (var $index = 0, $$l = self.list.length; $index < $$l; $index++)
{
var item = self.list[$index];
buf.push('<li');
buf.push(attrs2({ "class": ('item') }, {}));
buf.push('>');
var __val__ = item
buf.push(null == __val__ ? "" : __val__);
buf.push('</li>');
}
}
else
{
for (var $index in self.list)
{
if (self.list.hasOwnProperty($index))
{
var item = self.list[$index];
buf.push('<li');
buf.push(attrs2({ "class": ('item') }, {}));
buf.push('>');
var __val__ = item
buf.push(null == __val__ ? "" : __val__);
buf.push('</li>');
}
}
}
}).call(this);
buf.push('</ul></div>');return new Buffer(buf.join(""));
}
jt6 = function ()
{
//var attrs = jade.attrs, escape = jade.escape, rethrow = jade.rethrow;
//var self = locals || {};
var __val__;
s ='<div><h1' + attrs2({ "class": ('header') }, {}) + '>'
+ (null == (__val__ = self.header) ? "" : __val__) + '</h1><h2' + ({ "class": ('header2') }, {}) + '>'
+ (null == (__val__ = self.header2) ? "" : __val__) + '</h2><h3' + attrs2({ "class": ('header3') }, {}) + '>'
+ (null == (__val__ = self.header3) ? "" : __val__) + '</h3><h4' + attrs2({ "class": ('header4') }, {}) + '>'
+ (null == (__val__ = self.header4) ? "" : __val__) + '</h4><h5' + attrs2({ "class": ('header5') }, {}) + '>'
+ (null == (__val__ = self.header5) ? "" : __val__) + '</h5><h6' + attrs2({ "class": ('header6') }, {}) + '>'
+ (null == (__val__ = self.header6) ? "" : __val__) + '</h6><ul' + attrs2({ "class": ('list') }, {}) + '>';
// iterate self.list
(function()
{
var __val__;
if ('number' == typeof self.list.length)
{
for (var $index = 0, $$l = self.list.length; $index < $$l; $index++)
{
var item = self.list[$index];
s += '<li' + attrs2({ "class": ('item') }, {}) + '>' + (null == (__val__ = item) ? "" : __val__) + '</li>';
}
}
else
{
for (var $index in self.list)
{
if (self.list.hasOwnProperty($index))
{
var item = self.list[$index];
s += '<li' + attrs2({ "class": ('item') }, {}) + '>' + (null == (__val__ = item) ? "" : __val__) + '</li>';
}
}
}
}).call(this);
s += '</ul></div>';
return new Buffer(s);
}
var Benchmark = require('benchmark')
var suite = new Benchmark.Suite
suite
.add('jt1 - (b.push())', jt1)
.add('jt2 - (+= & +) ', jt2)
.add('jt3 - (+= only) ', jt3)
.add('jt4 - (+=, +, old attrs) ', jt4)
.add('jt5 - (b.push(), attrs2()) ', jt5)
.add('jt6 - (mostly +, attrs2()) ', jt6)
.on('cycle', function (event, bench) {
console.log(bench.toString());
})
.on('complete', function () {
console.log('Fastest is ' + this.filter('fastest').pluck('name'))
})
.run();
@saabi
Copy link
Author

saabi commented May 10, 2012

jt1 - (b.push()) x 28,025 ops/sec ±0.80% (58 runs sampled)
jt2 - (+= & +) x 45,956 ops/sec ±0.72% (62 runs sampled)
jt3 - (+= only) x 45,550 ops/sec ±0.76% (63 runs sampled)
jt4 - (+=, +, old attrs) x 30,269 ops/sec ±0.70% (62 runs sampled)
jt5 - (b.push(), attrs2()) x 41,582 ops/sec ±0.56% (63 runs sampled)
jt6 - (mostly +, attrs2()) x 46,342 ops/sec ±0.38% (60 runs sampled)
Fastest is jt2 - (+= & +) ,jt3 - (+= only) ,jt6 - (mostly +, attrs2())

@chowey
Copy link

chowey commented May 20, 2012

For your attrs2 code, shouldn't there be a space added to the start (not the end) for each new key/value that gets appended? This requires more concatenations which decreases performance but it is necessary to render right.

attrs2 = function (obj, escaped) {
  var terse = obj.terse;

  delete obj.terse;
  var keys = Object.keys(obj)
    , len = keys.length;

  var s = '';
  if (len) {
    for (var i = 0; i < len; ++i) {
      var key = keys[i]
        , val = obj[key];

      if ('boolean' == typeof val || null == val) {
        if (val) {
          terse
            ? (s+= ' ' + key)
            : (s+= ' ' + key + '="' + key + '"');
        }
      } else if (0 == key.indexOf('data') && 'string' != typeof val) {
        s += ' ' + key + '="' + JSON.stringify(val) + '"';
      } else if ('class' == key && Array.isArray(val)) {
        s += ' ' + key + '="' + exports.escape(val.join(' ')) + '"';
      } else if (escaped[key]) {
        s += ' ' + key + '="' + exports.escape(val) + '"';
      } else {
        s += ' ' + key + '="' + val + '"';
      }
    }
  }

  return s;
};

Otherwise you get things like <pclass="something" > instead of <p class="something">.

@saabi
Copy link
Author

saabi commented May 20, 2012

You're right. A little bug introduced when modifiying the old attrs() which joins with ' ' at the end.
Fixed by setting var s=' ';
We end up with an extra space at the end, which I'm leaving right now. However, removing it with substr is (edit:probably) faster than the extra concatenations.

Performance results shouldn't change significantly with this modification.

@chowey
Copy link

chowey commented May 21, 2012

My tests show that substr is slower. See this.

@saabi
Copy link
Author

saabi commented May 21, 2012

Good thing I came back to write 'probably' :)

However, do you think it's too sloppy to just leave that extra space at the end? I'll benchmark your attrs with mine to see if there's a significant difference. If not, then yours is preferable as it leaves cleaner HTML.

I thought that maybe because it possibly used the same character buffer for the new substring it would just be a matter of creating a new String object, hence it would be a fast operation. Maybe it does a copy operation to form the substring, collapsing the internal linked list.

@chowey
Copy link

chowey commented May 21, 2012

I think you'll need to have correct html that passes all the jade tests.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment