CanJS EJS Live Binding Performance

JavaScript performance comparison

Revision 22 of this test case created by

Preparation code

<script src="http://underscorejs.org/underscore-min.js">
</script>
<script src="http://yui.yahooapis.com/3.5.0pr2/build/yui/yui-min.js"></script>
<script src="http://zeptojs.com/zepto.min.js"></script>
<script src="http://canjs.us/release/latest/can.zepto.js"></script>
<script src="http://backbonejs.org/backbone-min.js">
</script>
<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js">
</script>


<style>
p {
  font: 12px/16px Arial;
  margin: 10px 10px 15px;    
}

button {
  font: bold 14px/14px Arial;  
  margin-left: 10px;
}

#grid {
  margin: 10px;   
}

.box-view {
  width: 20px; height: 20px;
  float: left;
  position: relative;
  margin: 8px;    
}

.box {
  border-radius: 100px;
  width: 20px; height: 10px;
  padding: 5px 0;
  color: #fff;
  font: 10px/10px Arial;
  text-align: center;
  position: absolute;
}   
​
</style>
<script type="text/ejs" id="ejs-template">
  <% for(var i = 0; i < boxes.length; i++){ %>
    <div class="box-view">
      <div class="box" id="box-<%= boxes[i].count  %>" style="<%= boxes[i].style() %>">
        <%= boxes[i].content() %>
      </div>
    </div>
  <% } %>
</script>

<script type="x-template" id="underscore-template">
  <div class="box" id="box-<%= number %>" style="top: <%= top %>px; left: <%= left %>px; background: rgb(0,0,<%= color %>);">
    <%= content %>
  </div>
</script>

<script type="text/x-handlebars" id="handlebars-template" data-template-name="box">
  <div class="box" {{bindAttr id="model.number" style="model.style"}}>
    {{ model.content }}
  </div>
</script>

<script type="text/x-template" id="knockout-template">
  <div data-bind="foreach: boxes">
    <div class="box-view">
      <div class="box" data-bind="style: {top: top(), left: left(), background: color()}, text: content()"></div>
    </div>
  </div>
</script>
<script type="text/x-yui-handlebars" id="yui-handlebars-template">
    <div class="box" id="box-{{number}}" style="top: {{top}}px; left: {{left}}px; background: rgb(0,0,{{color}});">
        {{content}}
    </div>
</script>
<div id="can-grid"></div>
<div id="bb-grid"></div>
<div id="ko-grid"></div>
<div id="yui-grid"></div>
<script>
    window.N = 100;
    
    (function(){
      var Box = function() {
          return {
            count: 0,
            top: function() {
              return (Math.sin(this.attr('count') / 10) * 10) + 'px';
            },
            left: function() {
              return (Math.cos(this.attr('count') / 10) * 10) + 'px';
            },
            color: function() {
              return 'rgb(0,0,' + (this.attr('count')) % 255 + ')';
            },
            content: function() {
              return this.attr('count') % 100;
            },
            tick: function() {
              this.attr('count', (this.attr('count') + 1));
            },
            style: function() {
              return 'top: ' + this.top() + '; left: ' + this.left() + '; background: ' + this.color() + ';';
            }
          };
        };
        var boxes;
        window.canInit = function() {
          boxes = new can.Observe.List();
          for (var i = 0; i < N; i++) {
            boxes.push(new Box());
          }
          $('#can-grid').append(can.view('ejs-template', {
            boxes: boxes
          }));
          };
          var canAnimate = function() {
            can.Observe.startBatch();
            for (var i = 0; i < N; i++) {
              boxes[i].tick();
            }
            can.Observe.stopBatch();
          }
      window.runCan = function() {
        canAnimate();
      };
    })();
    
    (function() {
    
      var Box = Backbone.Model.extend({
    
        defaults: {
          top: 0,
          left: 0,
          color: 0,
          content: 0
        },
    
        initialize: function() {
          this.count = 0;
        },
    
        tick: function() {
          var count = this.count += 1;
          this.set({
            top: Math.sin(count / 10) * 10,
            left: Math.cos(count / 10) * 10,
            color: (count) % 255,
            content: count % 100
          });
        }
    
      });
    
    
      var BoxView = Backbone.View.extend({
    
        className: 'box-view',
    
        template: _.template($('#underscore-template').html()),
    
        initialize: function() {
          this.model.bind('change', this.render, this);
        },
    
        render: function() {
          this.$el.html(this.template(this.model.attributes));
          return this;
        }
    
      });
    
      var boxes;
    
      window.backboneInit = function() {
          boxes = _.map(_.range(N), function(i) {
            var box = new Box({
              number: i
            });
            var view = new BoxView({
              model: box
            });
            $('#bb-grid').append(view.render().el);
            return box;
          });
          };
    
      var backboneAnimate = function() {
            for (var i = 0, l = boxes.length; i < l; i++) {
              boxes[i].tick();
            }
          };
    
      window.runBackbone = function() {
        backboneAnimate();
      };
    })();
    
    (function() {
    
      var Box = function() {
          this.count = ko.observable(0);
          this.top = function() {
            return (Math.sin(this.count() / 10) * 10) + 'px';
          };
          this.left = function() {
            return (Math.cos(this.count() / 10) * 10) + 'px';
          };
          this.color = function() {
            return 'rgb(0,0,' + (this.count()) % 255 + ')';
          };
          this.content = function() {
            return this.count() % 100;
          };
    
    
          this.tick = function() {
            this.count(this.count() + 1);
          }
          }
          
          
          
          
          
      var ViewModel = function(num) {
          this.num = num;
          this.boxes = ko.observableArray();
          for (var i = 0; i < num; i++) {
            this.boxes.push(new Box())
          }
          }
          
          
          
          
          
      var knockoutAnimate = function() {
            for (var i = 0, l = vm.boxes().length; i < l; i++) {
              vm.boxes()[i].tick();
            }
      }
          
          
          window.knockoutInit = function(){
            $('#ko-grid').html($('#knockout-template').html());
            vm = new ViewModel(N)
            ko.applyBindings(vm, $('#ko-grid')[0]);
          };
          
          
      window.runKnockout = function() {
        knockoutAnimate();
      };
    
    })();
    
YUI().use('model', 'view', 'handlebars', function (Y) {

Y.Box = Y.Base.create('box', Y.Model, [], {
    initializer: function () {
        this.count = 0;
    },
    
    tick: function () {
        var count = this.count += 1;
        
        this.setAttrs({
            top    : Math.sin(count / 10) * 10,
            left   : Math.cos(count / 10) * 10,
            color  : (count) % 255,
            content: count % 100
        });
    }        
}, {
    ATTRS: {
        top    : {value: 0},
        left   : {value: 0},
        color  : {value: 0},
        content: {value: 0},
        number : {}
    }
});
    
Y.BoxView = Y.Base.create('boxView', Y.View, [], {
    template: Y.Handlebars.compile(Y.one('#yui-handlebars-template').getContent()),
    
    initializer: function () {
        this.get('model').after('change', this.render, this);
    },
    
    create: function () {
        return Y.Node.create('<div class="box-view" />');
    },
    
    render: function () {
        var content = this.template(this.get('model').toJSON());
        this.get('container').setContent(content);
        return this;
    }
});

var boxes;

var yuiAnimate = function() {
    for (var i = 0, l = boxes.length; i < l; i++) {
      boxes[i].tick();   
    }
};

window.yuiInit = function () {
    boxes = _.map(_.range(N), function (i) {
        var box  = new Y.Box({number: i});
        var view = new Y.BoxView({model: box});
        Y.one('#yui-grid').append(view.render().get('container'));
        return box;
    });
};

window.runYUI = function() {
  yuiAnimate();    
};
    
});
    </script>
      
<script>
Benchmark.prototype.setup = function() {
  window.canInit();
      window.backboneInit();
      window.knockoutInit();
  window.yuiInit();

};

Benchmark.prototype.teardown = function() {
  $('#can-grid').empty();
  $('#bb-grid').empty();
  $('#ko-grid').empty();
  $('#yui-grid').empty();

};
</script>

Preparation code output

<script src="http://yui.yahooapis.com/3.5.0pr2/build/yui/yui-min.js"></script> <script src="http://zeptojs.com/zepto.min.js"></script> <script src="http://canjs.us/release/latest/can.zepto.js"></script> <script src="http://backbonejs.org/backbone-min.js"> </script> <script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js"> </script> <style> p { font: 12px/16px Arial; margin: 10px 10px 15px; } button { font: bold 14px/14px Arial; margin-left: 10px; } #grid { margin: 10px; } .box-view { width: 20px; height: 20px; float: left; position: relative; margin: 8px; } .box { border-radius: 100px; width: 20px; height: 10px; padding: 5px 0; color: #fff; font: 10px/10px Arial; text-align: center; position: absolute; } ​ </style> <script type="text/ejs" id="ejs-template"> <% for(var i = 0; i < boxes.length; i++){ %> <div class="box-view"> <div class="box" id="box-<%= boxes[i].count %>" style="<%= boxes[i].style() %>"> <%= boxes[i].content() %> </div> </div> <% } %> </script> <script type="x-template" id="underscore-template"> <div class="box" id="box-<%= number %>" style="top: <%= top %>px; left: <%= left %>px; background: rgb(0,0,<%= color %>);"> <%= content %> </div> </script> <script type="text/x-handlebars" id="handlebars-template" data-template-name="box"> <div class="box" {{bindAttr id="model.number" style="model.style"}}> {{ model.content }} </div> </script> <script type="text/x-template" id="knockout-template"> <div data-bind="foreach: boxes"> <div class="box-view"> <div class="box" data-bind="style: {top: top(), left: left(), background: color()}, text: content()"></div> </div> </div> </script> <script type="text/x-yui-handlebars" id="yui-handlebars-template"> <div class="box" id="box-{{number}}" style="top: {{top}}px; left: {{left}}px; background: rgb(0,0,{{color}});"> {{content}} </div> </script> <div id="can-grid"></div> <div id="bb-grid"></div> <div id="ko-grid"></div> <div id="yui-grid"></div> <script> window.N = 100; (function(){ var Box = function() { return { count: 0, top: function() { return (Math.sin(this.attr('count') / 10) * 10) + 'px'; }, left: function() { return (Math.cos(this.attr('count') / 10) * 10) + 'px'; }, color: function() { return 'rgb(0,0,' + (this.attr('count')) % 255 + ')'; }, content: function() { return this.attr('count') % 100; }, tick: function() { this.attr('count', (this.attr('count') + 1)); }, style: function() { return 'top: ' + this.top() + '; left: ' + this.left() + '; background: ' + this.color() + ';'; } }; }; var boxes; window.canInit = function() { boxes = new can.Observe.List(); for (var i = 0; i < N; i++) { boxes.push(new Box()); } $('#can-grid').append(can.view('ejs-template', { boxes: boxes })); }; var canAnimate = function() { can.Observe.startBatch(); for (var i = 0; i < N; i++) { boxes[i].tick(); } can.Observe.stopBatch(); } window.runCan = function() { canAnimate(); }; })(); (function() { var Box = Backbone.Model.extend({ defaults: { top: 0, left: 0, color: 0, content: 0 }, initialize: function() { this.count = 0; }, tick: function() { var count = this.count += 1; this.set({ top: Math.sin(count / 10) * 10, left: Math.cos(count / 10) * 10, color: (count) % 255, content: count % 100 }); } }); var BoxView = Backbone.View.extend({ className: 'box-view', template: _.template($('#underscore-template').html()), initialize: function() { this.model.bind('change', this.render, this); }, render: function() { this.$el.html(this.template(this.model.attributes)); return this; } }); var boxes; window.backboneInit = function() { boxes = _.map(_.range(N), function(i) { var box = new Box({ number: i }); var view = new BoxView({ model: box }); $('#bb-grid').append(view.render().el); return box; }); }; var backboneAnimate = function() { for (var i = 0, l = boxes.length; i < l; i++) { boxes[i].tick(); } }; window.runBackbone = function() { backboneAnimate(); }; })(); (function() { var Box = function() { this.count = ko.observable(0); this.top = function() { return (Math.sin(this.count() / 10) * 10) + 'px'; }; this.left = function() { return (Math.cos(this.count() / 10) * 10) + 'px'; }; this.color = function() { return 'rgb(0,0,' + (this.count()) % 255 + ')'; }; this.content = function() { return this.count() % 100; }; this.tick = function() { this.count(this.count() + 1); } } var ViewModel = function(num) { this.num = num; this.boxes = ko.observableArray(); for (var i = 0; i < num; i++) { this.boxes.push(new Box()) } } var knockoutAnimate = function() { for (var i = 0, l = vm.boxes().length; i < l; i++) { vm.boxes()[i].tick(); } } window.knockoutInit = function(){ $('#ko-grid').html($('#knockout-template').html()); vm = new ViewModel(N) ko.applyBindings(vm, $('#ko-grid')[0]); }; window.runKnockout = function() { knockoutAnimate(); }; })(); YUI().use('model', 'view', 'handlebars', function (Y) { Y.Box = Y.Base.create('box', Y.Model, [], { initializer: function () { this.count = 0; }, tick: function () { var count = this.count += 1; this.setAttrs({ top : Math.sin(count / 10) * 10, left : Math.cos(count / 10) * 10, color : (count) % 255, content: count % 100 }); } }, { ATTRS: { top : {value: 0}, left : {value: 0}, color : {value: 0}, content: {value: 0}, number : {} } }); Y.BoxView = Y.Base.create('boxView', Y.View, [], { template: Y.Handlebars.compile(Y.one('#yui-handlebars-template').getContent()), initializer: function () { this.get('model').after('change', this.render, this); }, create: function () { return Y.Node.create('<div class="box-view" />'); }, render: function () { var content = this.template(this.get('model').toJSON()); this.get('container').setContent(content); return this; } }); var boxes; var yuiAnimate = function() { for (var i = 0, l = boxes.length; i < l; i++) { boxes[i].tick(); } }; window.yuiInit = function () { boxes = _.map(_.range(N), function (i) { var box = new Y.Box({number: i}); var view = new Y.BoxView({model: box}); Y.one('#yui-grid').append(view.render().get('container')); return box; }); }; window.runYUI = function() { yuiAnimate(); }; }); </script>

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in CCBot 2.0.0 / Other 0.0.0
Test Ops/sec
CanJS
window.runCan()
pending…
Backbone
window.runBackbone();
pending…
Knockout
window.runKnockout();
pending…
YUI
window.runYUI();
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL.

0 Comments