惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
News and Events Feed by Topic
GbyAI
GbyAI
博客园 - Franky
宝玉的分享
宝玉的分享
Blog — PlanetScale
Blog — PlanetScale
Google DeepMind News
Google DeepMind News
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
人人都是产品经理
人人都是产品经理
Microsoft Azure Blog
Microsoft Azure Blog
The Register - Security
The Register - Security
腾讯CDC
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
I
InfoQ
The Cloudflare Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Engineering at Meta
Engineering at Meta
MongoDB | Blog
MongoDB | Blog
有赞技术团队
有赞技术团队
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
F
Full Disclosure
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Vercel News
Vercel News
博客园 - 【当耐特】
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Schneier on Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Project Zero
Project Zero
量子位
M
MIT News - Artificial intelligence
Stack Overflow Blog
Stack Overflow Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
美团技术团队
Attack and Defense Labs
Attack and Defense Labs
C
Cybersecurity and Infrastructure Security Agency CISA
T
The Blog of Author Tim Ferriss
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Troy Hunt's Blog
Google Online Security Blog
Google Online Security Blog
罗磊的独立博客
P
Proofpoint News Feed
Schneier on Security
Schneier on Security
Spread Privacy
Spread Privacy
S
SegmentFault 最新的问题
L
LINUX DO - 最新话题
Simon Willison's Weblog
Simon Willison's Weblog
爱范儿
爱范儿
博客园 - 聂微东
A
About on SuperTechFans
PCI Perspectives
PCI Perspectives
D
Docker

Minko Gechev's blog

skillgrade Unit Tests for AI Agent Skills You Should Care About AI Generative Development LLM-first Web Framework Reactive framework in ~200 lines of JavaScript Managing Angular Are LLMs going to replace us? Prefetching Heuristics Design Patterns in Open Source Projects - Part II Design Patterns in Open Source Projects - Part I What I learned doing 125 public talks - Part I Dynamic imports solve all the problems, right? 5 Angular CLI Features You Didn't Know About Angular quicklink Preloading Strategy Introducing Bazel Schematics for Angular CLI Building TypeScript Projects with Bazel Joining Google Playing Mortal Kombat with TensorFlow.js. Transfer learning and data augmentation Fast, extensible, configurable, and beautiful linter for Go Introducing Guess.js - a toolkit for enabling data-driven user-experiences on the Web Machine Learning-Driven Bundling. The Future of JavaScript Tooling. JavaScript Decorators for Declarative and Readable Code 3 Tricks For Using Redux and Immutable.js with TypeScript Follow Your Dream Career with Open Source. Personal Story. Redux Anti-Patterns - Part 1. State Management. Faster Angular Applications - Understanding Differs. Developing a Custom IterableDiffer Faster Angular Applications - Part 2. Pure Pipes, Pure Functions and Memoization Faster Angular Applications - Part 1. On Push Change Detection and Immutability Understanding Dynamic Scoping and TemplateRef Implementing a Simple Compiler on 25 Lines of JavaScript Developing Statically Typed Programming Language WebVR for a Gamified IDE 7 Angular Tools That You Should Consider Announcing ngrev - Reverse Engineering Tool for Angular Implementing Angular's Dependency Injection in React. Understanding Element Injectors. Distributing an Angular Library - The Brief Guide Angular in Production Ahead-of-Time Compilation in Angular 2.5X Smaller Angular 2 Applications with Google Closure Compiler Using Stripe with Angular (Deprecated) Building an Angular Application for Production Implementing the Missing "resolve" Feature of the Angular 2 Router Scalable Single-Page Application Architecture Managing ambient type definitions and dealing with the "Duplicate identifier" TypeScript error Static Code Analysis of Angular 2 and TypeScript Projects Enforcing Best Practices with Static Code Analysis of Angular 2 Projects ViewChildren and ContentChildren in Angular Dynamically Configuring the Angular's Router Angular 2 Hot Loader Lazy Loading of Route Components in Angular 2 Aspect-Oriented Programming in JavaScript Flux in Depth. Store and Network Communication. Using JSX with TypeScript Flux in Depth. Overview and Components. Even Faster AngularJS Data Structures Boost the Performance of an AngularJS Application Using Immutable Data - Part 2 Angular2 - First Impressions Build Your own Simplified AngularJS in 200 Lines of JavaScript Persistent State of ReactJS Component Processing Binary Protocols with Client-Side JavaScript Stream your Desktop to HTML5 Video Element Multi-User Video Conference with WebRTC Asynchronous calls with ES6 generators Binary Tree iterator with ES6 generators WebRTC chat with React.js AngularJS in Patterns (Part 3) AngularJS in Patterns (Part 2). Services. Using GitHub Pages with Jekyll! AngularJS in Patterns (Part 1). Overview of AngularJS Singleton in JavaScript Express over HTTPS What I get from the JavaScript MV* frameworks Remote Desktop Client with AngularJS and Yeoman The magic of $resource (or simply a client-side Active Record) AngularJS Inheritance Patterns AngularAOP v0.1.0 Advanced JavaScript at Sofia University AngularJS style guide Lazy prefetching of AngularJS partials VNC client on 200 lines of JavaScript Aspect-Oriented Programming with AngularJS CSS3 flipping effect Practical programming with JavaScript Why I should use publish/subscribe in JavaScript JavaScript, the weird parts Functional programming with JavaScript plainvm Looking for performance? Probably you should NOT use [].sort (V8) JavaScript image scaling ELang Caching CSS with localStorage Self-invoking functions in JavaScript (or Immediately Invoked Function Expressions) Asus N56VZ + Ubuntu 12.04 (en) Asus N56VZ + Ubuntu 12.04 Debian Squeeze + LXDE on Google Nexus S (or having some fun while suffering) HTML5 image editor Курсови проекти – ФМИ Carousel Gallery SofiaJS...
Boost the Performance of an AngularJS Application Using Immutable Data
2015-03-02 · via Minko Gechev's blog

Edit · Mar 2, 2015 · 11 minutes read · Follow @mgechev Immutable.js JavaScript AngularJS

I have affinity to functional programming since my first year in college. During my initial contact with a purely functional programming language (Haskell in my case) I didn’t really understand all the advantages it provides, everything was reduced to just writing a cool recursive functions and solving algorithmic problems.

Later, when my code was used in production, I started appreciating things like high-order functions, closures, currying, etc. I even wrote a blog post on topic “Functional programming with JavaScript”.

Last couple of years I had the hard task to write a complex UI. A lot of user inputs, which can change the model from different places and potentially lead to inconsistent state of the application, a lot of bugs, which are hard to debug and find. Later ReactJS was released and I noticed the concept of the “pure functions” in their UI components. A react component gets rendered the same way when it receives the same input parameters and it has the same state (if you implement it properly). React even provides the PureRenderMixin, which can make the “pure component” rendering even faster!

React is awesome, there are no two opinions. I’m also huge AngularJS fan. A few years ago I wrote the first AngularJS Style Guide, “AngularJS in Patterns”, AngularAOP and a few other AngularJS modules/components/examples, which got popular. So I started wondering whether I can use the same idea of immutability of the model in AngularJS, at least for accelerating the data-binding watchers.

Immutable.js

On 28 of May, 2014, the first commit of Immutable.js was pushed in the facebook’s organization on GitHub. Immutable.js is a set of immutable data structures (List, Set, Map, etc.) implemented in JavaScript. What exactly is an immutable data structure? Well it is a data structure, which can’t change. Each action, which intends to change the collection creates a new one.

let list = Immutable.List([1, 2, 3]);
let changed = list.push(4);
list.toString();    // List [ 1, 2, 3 ]
changed.toString(); // List [ 1, 2, 3, 4 ]
list === changed    // false

Compared to the mutable JavaScript lists:

let list = [1, 2, 3];
list.push(4);
console.log(list); // [1, 2, 3, 4]

AngularJS data-binding

There are a lot of posts about how the AngularJS data-binding and dirty checking works. I even created a light AngularJS implementation in order to illustrate it better. Basically it involves a lot of evaluations and comparisons of the watched expression and their results. For example:

$scope.collection = generateHugeCollection();
$scope.$watchCollection('collection', function (val) {
  // do some stuff with the changed value
});

Once we register watcher, which watches the expression 'collection', the expression gets evaluated at least once on each $digest loop and its current value gets compared to the previous value on each evaluation. The evaluation of this expression is with constant complexity (O(1)), since it only involves lookup of the property collection, but the equality check has a linear complexity (O(n)) (in case $watchCollection is used, otherwise it could be worst).

Now imagine we have a few watchers of the same collection - in a directive, service and controller. This happens quite often, for example if you have a few bindings in the UI to the same value. This means that on each $digest loop we will need to:

  • evaluate the expression
  • compare the result with the previous value of the evaluation of the same expression

At least n times for n bindings.

How can Immutable.js help?

Since each immutable data-structure creates a new instance of itself on change, we basically get different references when we add or remove elements. This drops the complexity of $watch to O(1), since now we don’t need to loop over the entire collection in order to find the difference with the previous value. We simple compare the reference of the current collection with the previous one:

previous === current // O(1)

Immutable.js and AngularJS

Lets create a simple example in which we bind with ng-repeat to immutable list and render it in our HTML page:

var app = angular.module('sampleApp', []);

let SampleCtrl = ($scope) => {
  $scope.list = Immutable.List([1, 2, 3]);
};

app.controller('SampleCtrl', SampleCtrl);
<!DOCTYPE html>
<html lang="en">
<head>
  <title></title>
</head>
<body ng-app="sampleApp" ng-controller="SampleCtrl">
  <ul>
    <li ng-repeat="item in list" ng-bind="item"></li>
  </ul>
</body>
</html>

Result:

  • false
  •  
  •  
  • 3
  • 5
  • 0
  •  
  • [object Object]
  • 3

Not exactly what we wanted, right? What Immutable.js does is to wrap the plain JavaScript collection in a proxy. So using the code above we don’t iterate over the collection’s elements but over the Immutable.js’ object properties instead. Each immutable collection has a method called toJS, which returns the JavaScript representation of the immutable data structure.

What we can do now? Well, we can simply watch $scope.list.toJS() instead of only $scope.list. Anyway, this will be far from effective:

let list = Immutable.List([1, 2, 3]);
let jsList = list.toJS();
list.toJS() === jsList // false

This mean that Immutable.js creates a new JavaScript object for each call of toJS. Another thing we can do is to watch the inner collection, which is inside the immutable wrapper:

$scope.$watchCollection(function () {
  return $scope.list._tail.array;
}, function (val) {
  // do something with the changed value
});

Each time you watch a private property a kitty, somewhere, suffer! There are two reasons this is a bad choice:

  • This is a private property…The underlaying implementation of Immutable.js may change so your code will break.
  • This is a private property…There’s a property with different name for each immutable data collection (Map, Set, List…).

So we definitely need to watch the immutable collection, this way we will:

  • Take advantage of its immutability by checking whether it has changed with a constant complexity (comparing the references).
  • Use the public API, so we won’t make any kitties suffer!

Angular Immutable

In order to deal with this issue, I created a simple directive, which allows binding to Immutable.js collections. It’s called angular-immutable and could be found in my GitHub account (also published as bower and npm modules angular-immutable).

Lets take a look at the code example, which uses angular-immutable:

var app = angular.module('sampleApp', ['immutable']);

let SampleCtrl = ($scope) => {
  $scope.list = Immutable.List([1, 2, 3]);
};

app.controller('SampleCtrl', SampleCtrl);
<!DOCTYPE html>
<html lang="en">
<head>
  <title></title>
</head>
<body ng-app="sampleApp" ng-controller="SampleCtrl">
  <ul>
    <li immutable="list" ng-repeat="item in list" ng-bind="item"></li>
  </ul>
</body>
</html>

Result:

  • 1
  • 2
  • 3

With only two slight changes we made it work! All we did was:

  • Include the module immutable as dependency
  • Add the directive immutable as an attribute, which points to the name of the immutable property of the scope

Angular Immutable Implementation

Since the whole library is implemented in only a few lines of code lets take a look at the immutable directive’s source code:

/* global angular */

var immutableDirective = () => {
  let priority = 2000;
  let scope = true;
  let link = (scope, el, attrs) => {
    let { immutable } = attrs;
    if (!(/^[a-zA-Z0-9_$]+$/).test(immutable)) {
      return;
    }
    if (!scope[immutable]) {
      console.warn(`No ${immutable} property found.`);
    }
    scope.$watch(() => {
      return scope.$parent[immutable];
    }, (val) => {
      scope[immutable] = val.toJS();
    });
  };
  return { priority, scope, link };
};

angular.module('immutable', [])
  .directive('immutable', immutableDirective);

When the immutableDirective is used as an attribute, it accepts as value the name of the immutable property. Later, it adds watcher for this property. Once the immutable data “changes” we get a new reference, so the watcher can perform the check with a constant complexity (only returns scope.$parent[immutable], which returns a reference, so Angular can use === for comparison).

This way we:

  • Take advantage of the immutability of the property by improving the runtime of the $watch function
  • Do not change the implementation of already existing software (do not change ng-repeat neither Immutable.js), so we follow the Open-Closed principle
  • We don’t use any private properties

This approach has some limitations. For example, if the immutable value is result of some complex expression we can’t do anything.

Benchmarks

Using immutable data structures seems exciting and amazing! Anyway, it has some drawbacks. The $watch expressions became extremely fast but there’s a big overhead of creating a new data structure once we add or remove items. This gets even slower when we have a complex nested composition.

So we have:

  • Fast change detection
  • Slow insertions and deletions

Definitely, this approach will be useful when we have a lot of watchers for a huge data-structure, which rarely changes. But how huge and how fast it can get?

In the benchmarks below I tried to find these answers.

Test cases

I run 24 tests - 12 using immutable list and 12 using plain JavaScript array. Since the biggest factors in this benchmark are the number of bindings and the collection size I did the cross product between:

Bindings

  • 1
  • 5
  • 10
  • 20

Collection Size

  • 100
  • 1k
  • 10k

The code I run is:

function SampleCtrl($scope, $timeout) {
  'use strict';
  // Current runs count
  var runs = 0;

  // Defines the amount of changes of the array
  var TOTAL = 500;
  var start = Date.now();
  $scope.$watchCollection('list', function () {});

  // Generates a random collection
  $scope.list = buildCollection(SIZE);
  function changeCollection() {
    if (runs >= TOTAL) {
      console.log('%cDone!',
          'font-size: 50px; color: blue;' +
          'font-weight: bold; font-family: impact;');
      console.log('%c' + ((Date.now() - start) / 1000) +
          ' seconds required.', 'font-size: 30px; color: red;');
      return;
    }
    $timeout(function () {
      // Changes random index of the array
      var idx = Math.floor(Math.random() * (SIZE - 1));
      $scope.list[idx] = Math.random();
      runs += 1;
      changeCollection();
    }, 0);
  }
  changeCollection();
}

The code for Immutable.js is similar except that the body of $timeout looks like:

var idx = Math.floor(Math.random() * (SIZE - 1));
$scope.list = $scope.list.set(idx, Math.random());

And here is the markup, which I used:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body ng-app="sampleApp" ng-controller="SampleCtrl">
<script src="/scripts/all.js"></script>
</body>
</html>

Results

Plain JavaScript array

Here are the results I got from running the benchmark with plain JavaScript array:

1 5 10 20
100 2.517 2.56 2.573 2.58
1000 2.555 2.675 2.747 2.853
10000 2.861 4.025 7.736 15.68

When the collection gets bigger the test case running time gets slower. When we increase the bindings (watchers), everything gets even slower because of the additional iterations performed by $watchCollection.

Immutable JavaScript list

Here are the results running the same code with immutable list:

1 5 10 20
100 2.696 2.507 2.562 2.569
1000 2.715 2.54 2.569 2.49
10000 2.832 2.538 2.599 2.708

We see how much better performance the Immutable.js collection has. Once we increase the collection size and the number of watchers the running time of the Plain JavaScript array test case grows exponentially. In the case of immutable data the running time stays stable.

On the other hand, since when using immutable list, the watcher runs with a constant complexity we have only the simple overhead caused by creating a new data-structure on each change.

DOM rendering

Lets explore what will happen if we render the collection we used for profiling. For testing the immutable list I used this markup:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body ng-app="sampleApp" ng-controller="SampleCtrl">
<ul>
  <li immutable="list" ng-repeat="item in list track by $index" ng-bind="item"></li>
</ul>
<script src="/scripts/all.js"></script>
</body>
</html>

And for testing the plain JavaScript array I used the same markup with the immutable attribute removed. I changed the parameters of these test cases to:

Collection Size

  • 10k

Bindings count

  • 1
  • 5
  • 15
  • 25
  • 30

The watcher added by ng-repeat is being ignored for now.

And here are the results:

1 5 15 25 30
Immutable 14.714 14.296 14.8 14.489 14.331
Plain 13.308 15.689 23.415 19.986 27.526

Here is a chart for better understanding of the benchmark:

Initially the plain JavaScript array does better but once we increase the number of bindings the performance decreases dramatically.

But lets go a little bit further…Lets do CPU profiling for the test cases, which runs with immutable collection and 30 watchers.

The biggest slowdown comes from the watcher added by ng-repeat. Lets dig into AngularJS’s source code and change that watcher to a simple $watch instead of $watchCollection (do not do this in AngularJS copy you are going to use in production otherwise ng-repeat’s binding will not work with mutable data structures) and see what will happen…

After running the 30 bindings benchmark with the immutable list with changed ng-repeat’s implementation, we got almost 1 second improvement!

Heap Profiling

In order to make this performance test complete, lets profile the memory usage!

When running the Chrome DevTools Heap Profiler, in the test case with 30 watchers and plain JavaScript object, we get pretty low memory usage:

As expected, when using Immutable.js the memory usage is a bit higher because of the overhead of creating new data structure on change.

Conclusion

The benchmarks I did aren’t using the best profiling APIs available nowadays, probably a better idea would be to use benchpress instead but they still give quite accurate perspective of when it is reasonable to use immutable data structures in an AngularJS application.

The main content of the blog post clearly shows that, when dealing with a big data set with a lot of bindings it is definitely a good idea to make it immutable, especially if it changes rarely. Model’s immutability boost the performance of the watchers, checking for change, by allowing them to use simple equality check instead of linear algorithm.

On the other hand, we noticed that we have a little overhead by the creation of the immutable data on change. We also have a slightly bigger memory usage, which when dealing with enormous amount data might be a deal breaker.

Place for improvement

In the package angular-immutable I implemented a filter, which allows iterating over immutable data structures and binding to them (using ng-repeat, ng-bind, for example). Unfortunately, this filter allows only one-way data-binding. Two places for improvements are:

  • Allow two-way data-binding to immutable data
  • Implement optimized version of ng-repeat, which uses $watch instead of $watchCollection, for increasing the watcher’s speed.

Parts of the series: