Angular VS Knockout VS Ember VS React VS Mithril VS Mercury VS Ractive (v795)

Revision 795 of this benchmark created by abernh on


Description

This is a simple rendering speed test between the most popular libraries/frameworks. I added in Mithril because it's a newcomer with some promise.

UPD: AngularJS 1.4.8 Rev. 795: updated angular clear to use also data.splice(0,data.length) instead reset to new array [].

Added Mercury... Rev. 795: updated mercury uri (because of broken link) but commented out tests as they were broken - plz fix if you need them

UPD: ember Rev. 795: updated to v. 2.2.0 & handlebars 4.0.5 !! and added "emberDigest" to render like Angular only every 16ms

UPD: Knockout JS 3.4.0 Added Knockout Rate Limiting http://knockoutjs.com/documentation/rateLimit-observable.html

UPD: React 0.11.2

Added Reactive.js Rev. 795: updated reactive uri to 'latest'

Typo: Reactive is Ractive

Preparation HTML

<!-- Jquery -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.0.5/handlebars.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/2.2.0/ember.prod.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ember.js/2.2.0/ember-template-compiler.js"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.11.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mithril/0.1.22/mithril.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/0.10.5/vue.min.js"></script>
<script src="http://cdn.ractivejs.org/latest/ractive.min.js"></script>
<!-- Mercury -->
<!-- script src="http://raynos.github.io/mercury/index.js"></script -->

<!-- Angular -->
<div ng-app="ngApp">
    Angular 1.4.8 (using track by $index):
    <span ng-controller="Ctrl" id="angList"><span ng-repeat="item in data track by $index">{{item}}</span></span>
</div>

<!-- Knockout -->
<div id="koapp">
    Knockout:
    <span data-bind="foreach: data"><span data-bind="text: $data"></span></span>
</div>

<div id="koappRL">
    Knockout:
    <span data-bind="foreach: data"><span data-bind="text: $data"></span></span>
</div>

<!-- Ember -->

<div id="emapp">
    <script type="text/x-handlebars">
        Ember:
<span>
    {{#each EMapp.data}}<span>{{this}}</span>{{/each}}
</span>
    </script>
</div>

<!-- React -->
<div id="react">
    React: <span id="reactMountNode"></span>
</div>


<!-- Mithril -->
<div id="mithrilapp">
    Mithril: <span id="mithrilMountNode"></span>
</div>


<!-- Vuejs -->
<div id="vuejs">
    Vuejs: <span id="vuejsMountNode"><span v-repeat="item: data">{{ item }}</span></span>
</div>

<!-- Mercuryjs -->
<div id="mercuryjs">
    Mercuryjs: <span id="mercuryjsMountNode"></span>
</div>

<!-- Ractive -->
<div id="ractive">
    Ractive: <span id="ractiveMountNode"></span>
</div>

<script>

    var timeout = 0;

    // ----------------- Ang ------------------------

    var ngApp = angular.module('ngApp', []);
    ngApp.controller('Ctrl', function ($scope) {
        $scope.data = [];
    });

    // ----------------- KO ------------------------
    var KOData = ko.observableArray();
    var KOviewmodel = {data: KOData};

    var ENV = {EXTEND_PROTOTYPES: false};

    // ----------------- React ------------------------

    var ReactData = [];
    var ReactUpdate = null;
    var ReactComponent = React.createClass({
        displayName: 'PerfTest',
        getInitialState: function () {
            return {data: ReactData};
        },
        componentDidMount: function () {
            ReactUpdate = this._onData;
        },
        render: function () {
            return (
                    React.DOM.span(null,
                            this.state.data.map(function (result) {
                                return React.DOM.span(null, result);
                            })
                    )
            );
        },
        _onData: function () {
            this.setState({data: ReactData});
        }
    });

    // ----------------- Mithril ------------------------
    var MithrilData = [];
    var mithapp = {
        controller: function () {
        },
        view: function (ctrl) {
            return m("span", [MithrilData.map(function (datum) {
                return m('span', datum);
            })]);
        }
    };

    // ----------------- Mercury ------------------------

    window.mercuryPush = function () {
    };
    window.mercuryClear = function () {
    };
    try {
        var h = mercury.h;
        var mdata = mercury.array([]);

        function mrender(data) {
            return h("span", data.map(function (datum) {
                return h('span', datum)
            }));
        }

        // Mercury test calls
        mercury.app(document.getElementById('mercuryjsMountNode'), mdata, mrender);
        window.mercuryClear = function () {
            mdata.splice(0, mdata.getLength())
        };

        window.mercuryPush = function (data) {
            mdata.push(data)
        }
    } catch (err) {
        console.log("mercury tests not loaded.", err);
    }

    // ----------------- Reactive------------------------



    // ############### doc ready : render ############################

    $(document).ready(function () {

        // ----------------- Ang ------------------------
        angular.element(document).ready(function () {

            var ang_scope = $('#angList').scope();

            window.ANGclear = function () {
                ang_scope.data.splice(0, ang_scope.data.length);
            };
            window.ANGpush = function (data) {
                ang_scope.data.push(data);
                if (Date.now() > (timeout + 16)) {
                    window.setTimeout(function () {
                        ang_scope.$digest();
                        //console.log("triggered digest");
                    }, 0);
                    timeout = Date.now() + 16;
                }
            };
        });

        // ----------------- KO ------------------------

        ko.applyBindings(KOviewmodel, document.getElementById('koapp'));
        window.KOclear = function () {
            KOData.splice(0, KOData().length);
        };
        window.KOpush = function (data) {
            KOData.push(data);
        };


        var KODataRL = ko.observableArray();
        KODataRL.extend({rateLimit: 0});
        var KOviewmodelRL = {data: KODataRL};

        ko.applyBindings(KOviewmodelRL, document.getElementById('koappRL'));
        window.KOclearRL = function () {
            KODataRL.splice(0, KOData().length);
        };
        window.KOpushRL = function (data) {
            KODataRL.push(data);
        };

        // ----------------- Ember ------------------------

        EMapp = Ember.Application.create({
            rootElement: '#emapp'
        });
        EMapp.data = Ember.A();

        var emberTimeout,
            emberDigestRunner = null,
            emberDigest = function(){
                var tmpNow = Date.now();

                if(emberTimeout){
                    if(emberTimeout < tmpNow + 32){


                        Ember.endPropertyChanges();
                        
                        emberTimeout = null;
                        window.clearTimeout(emberDigestRunner);
                    }else{
                        if(emberDigestRunner) return;
                        emberDigestRunner = window.setTimeout(function(){
                            emberDigest();
                        }, 32);
                        return;
                    }
                }

                if(!emberTimeout){
                    emberTimeout = tmpNow;
                    Ember.beginPropertyChanges();
                }
            };

        window.EMclear = function () {
            EMapp.data.clear();
            //emberDigest();
        };
        window.EMpush = function (data) {
            EMapp.data.pushObject(data);
            //emberDigest();
        };

        // ----------------- React ------------------------
        var reactComp = ReactComponent();
        React.renderComponent(reactComp, document.getElementById('reactMountNode'));

        window.RClear = function () {
            ReactData.splice(0, ReactData.length);
            ReactUpdate();
        };

        window.RPush = function (data) {
            ReactData.push(data);
            ReactUpdate();
        };

        // ----------------- Mithril ------------------------

        m.module(document.getElementById("mithrilMountNode"), mithapp);
        window.Mclear = function () {
            //MithrilData = [];
            MithrilData.splice(0, MithrilData.length);
        };

        window.Mpush = function (data) {
            MithrilData.push(data);

            if (Date.now() > (timeout + 16)) {
                window.setTimeout(function () {
                    m.startComputation();
                    m.endComputation();
                }, 0);

                timeout = Date.now();
            }

        };

        // ----------------- Vue ------------------------

        var vueInstance = new Vue({
            el: '#vuejsMountNode',
            data: {
                data: []
            }
        });

        window.VueClear = function () {
            //vueInstance.data = [];
            vueInstance.data.splice(0, vueInstance.data.length);
        };

        window.VuePush = function (data) {
            vueInstance.data.push(data);
        };

        // ----------------- Reactive ------------------------

        var RactiveData = [];
        window.RactiveClear = function () {
            RactiveData.splice(0, ReactData.length);
        };
        window.RactivePush = function (data) {
            RactiveData.push(data);
        };

        var RactiveComponent = new Ractive({
            el: 'ractiveMountNode',
            template: '{{#data}}{{.}}{{/data}}',
            data: {data: RactiveData}
        });
    });

</script>

Test runner

Ready to run.

Testing in
TestOps/sec
Angular
ANGclear();

for (var i = 0; i < 100; i++)
  ANGpush("nitem" + Date.now());
ready
Knockout
KOclear();
for (var i = 0; i < 100; i++)
  KOpush("kitem" + Date.now());
ready
Mithril
Mclear();
for (var i = 0; i < 100; i++)
  Mpush("mitem" + Date.now());
ready
vuejs
VueClear();
for (var i = 0; i < 100; i++)
  VuePush("vitem" + Date.now());
ready
Ember
EMclear();
for (var i = 0; i < 100; i++)
  EMpush("eitem" + Date.now());
ready
Ractive
RactiveClear();
for (var i = 0; i < 100; i++)
  RactivePush("aitem" + Date.now());
ready

Revisions

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