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

推荐订阅源

Forbes - Security
Forbes - Security
大猫的无限游戏
大猫的无限游戏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Jina AI
Jina AI
美团技术团队
博客园 - 聂微东
博客园 - 叶小钗
Security Latest
Security Latest
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
博客园_首页
Spread Privacy
Spread Privacy
J
Java Code Geeks
雷峰网
雷峰网
宝玉的分享
宝玉的分享
C
Cyber Attacks, Cyber Crime and Cyber Security
P
Privacy International News Feed
C
CXSECURITY Database RSS Feed - CXSecurity.com
T
Threat Research - Cisco Blogs
The Hacker News
The Hacker News
量子位
L
LINUX DO - 热门话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
G
GRAHAM CLULEY
D
Darknet – Hacking Tools, Hacker News & Cyber Security
月光博客
月光博客
腾讯CDC
Last Week in AI
Last Week in AI
人人都是产品经理
人人都是产品经理
酷 壳 – CoolShell
酷 壳 – CoolShell
T
Tor Project blog
罗磊的独立博客
V
Vulnerabilities – Threatpost
Apple Machine Learning Research
Apple Machine Learning Research
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
有赞技术团队
有赞技术团队
Project Zero
Project Zero
Hugging Face - Blog
Hugging Face - Blog
爱范儿
爱范儿
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tenable Blog
MyScale Blog
MyScale Blog
T
The Exploit Database - CXSecurity.com
GbyAI
GbyAI
博客园 - 【当耐特】
O
OpenAI News
Schneier on Security
Schneier on Security
S
Secure Thoughts
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
S
Securelist
博客园 - 司徒正美

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 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 Boost the Performance of an AngularJS Application Using Immutable Data 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...
Faster Angular Applications - Part 2. Pure Pipes, Pure Functions and Memoization
2017-11-12 · via Minko Gechev's blog

In this post, we’ll focus on techniques from functional programming we can apply to improve the performance of our applications, more specifically pure pipes, memoization, and referential transparency. If you haven’t read the first part of the “Faster Angular Applications” series, I’d recommend you to take a look at it or at least get familiar with the structure of the business application that we’re optimizing.

The code for this blog post is available at my GitHub account:

Goals for Optimization

In this section, we’ll see how we can reduce the number of computations going on in the application even further! The three specific features that we’re going to focus on are:

  1. Adding an employee to a department.
  2. Removing an employee from a department.
  3. Initial rendering performance.

Optimizing the Mutation Operations

There’s something in common between the first two points above - they mutate the list of employees for a specific department. Since in the previous blog post we switched to OnPush change detection strategy with Immutable.js, now if we add or remove an employee from any of the lists we’ll get a new immutable list. This will trigger the change detection for the EmployeeListComponent and its child components - ListComponent and NameListComponent which will require re-evaluation of all the expressions in the templates of these components.

Let’s see how this looks in practice. For the purpose, we can add a log statement in the ListComponent’s calculate method to see when it gets invoked. Here’s the final result:

From the GIF above we can see that while typing the change detection doesn’t get invoked - cool! We did this optimization in the previous post from the series.

We can also notice that when the user adds a new employee the calculate method gets invoked 71 times. Given that we initially have 70 employees on the sales list, this means that on top of calculating the numeric value for the new employee we also recalculate it for all the already existing employees. This way we perform 70 useless calculations which in case of a computationally intensive business calculation can have some serious performance implications!

What we’d want to happen is:

  • During the initial application rendering, we calculate the numeric values for all the employees and visualize them.
  • When we add a new entry, we calculate the numeric value only for the newly added employee, without performing any redundant calculations for the already existing employees, since we have their numeric values from the rendering phase.

To implement this behavior, let’s take a look at our mocked business calculation - the function of Fibonacci:

const fibonacci = (num: number): number => {
  if (num === 1 || num === 2) {
    return 1;
  }
  return fibonacci(num - 1) + fibonacci(num - 2);
};

It has two fundamental properties which are typical for all mathematical functions:

  • It does not perform any side effects - when calculating the employee’s numeric value, we don’t send any network requests, touch local storage, log anything in the console, etc.
  • It returns the exact same result for the same set of arguments - fibonacci(1) is always 1, same for fibonacci(n) which always is going to return the same k where n ∈ ℕ, k ∈ ℕ.

Such functions in the world of functional programming are called pure functions. All mathematical functions are pure, and for performing a meaningful business calculation for the employees’ numeric value, we’d most likely use a mathematical function.

Angular Pipes and Pure Functions

In Angular, on the other hand, we have pipes. Pipes are used for performing some kind of data processing, for instance, data formatting. There are two types of pipes:

  • Pure pipes - produce the same output when invoked with the same set of arguments. Such pipes hold the referential transparency property.
  • Impure pipes - can hold state and respectively produce different output for the same set of arguments.

Examples of pure pipes are the DecimalPipe and DatePipe pipes. Every time when we invoke the DecimalPipe with given number and formatting configuration, we’ll always get the exact same string as a result.

An example for impure pipe is the async pipe for instance, which holds internal state. More about pure and impure pipes you can find in my book “Switching to Angular”.

Alright, so we have pure and impure pipes but how does this matter for Angular? Well:

Angular is going to evaluate given pure pipe call only if it has received different arguments compared to its previous invocation.

Angular is going to determine if the arguments of the pipe are different by performing an equality check. This means that, if we have:

{{ 2.718281828459045 | number:'3.1-5' }}

Angular is going to evaluate the expression once and produce the string 2.718. The next time when the change detection runs, Angular will get the last result from the evaluation since the arguments of the pure pipe haven’t changed!

Optimizing Expression Re-Evaluation with Pure Pipes

Wait, isn’t that exactly what we want to do in our application - do not re-evaluate the employee’s numeric value unless it gets changed? This means that we can encapsulate our business calculation inside of a pure pipe and as a bonus get the caching that Angular provides.

Here’s how we can proceed:

@Pipe({
  name: 'calculate',
  pure: true
})
export class CalculatePipe {
  transform(num: number) {
    return fibonacci(num);
  }
}

In fact, all pipes in Angular are pure by default so we can drop the pure: true property.

Now we need to modify the template of the ListComponent a little bit to invoke the pipe instead of the calculate method defined inside of its controller:

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    ...
     {{ item.num | calculate }}
    ...
  `
})
export class ListComponent { ... }

To verify if the last optimization improved the application’s performance. I wrote another e2e test which adds and removes employees from the sales department. After running it with benchpress, I got the following results:

With the latest optimization, the time required for running the tests dropped from 394.53ms to 9.70ms! That is a huge improvement! We achieved it only by performing a small refactoring, without writing any custom code, just taking advantage of what Angular already provides for free!

Initial Application Rendering

For this performance benchmark we’ll increase the number of items from 140 to 1,000. Lets see how this is going to affect our application’s rendering performance:

9.53s is quite a lot for initial rendering time. In fact…

Waiting for slow web pages to load is as stressful as watching a horror movie: https://t.co/spwEC1P9ct #perfmatters pic.twitter.com/3JmcGPZij2

— Addy Osmani (@addyosmani) March 19, 2016

In our case we’re even not waiting for the application to get loaded, we’re waiting for it to get rendered. Let’s suppose we have put a lot of effort into optimizing the network performance of the application and our bundle size is only 50KB, we download all the resources from the network for less than 100ms. This sounds great, right? Well, after that the user needs to wait another 9430ms in order see the application rendered.

There are various techniques to fix this - we don’t have to render all the 1,000 employees at once, we can, for instance, apply pagination or render the items lazily when the user scrolls. But there’s also something else we can apply in this specific case. For this purpose, let’s take a look at the numeric data for the employees:

We have a lot of employees with the same numeric values which produce the same output after going through the business calculation. We can conclude that in this case, we have samples in a small range. Lets for a second remember what optimization Angular performs with pure pipes:

Angular is going to evaluate given pure pipe call only if it has received different arguments compared to its previous invocation.

This doesn’t say anywhere that Angular is not going to evaluate two pipe expressions in different places in the component tree which have the same arguments. So, if we have:

<md-list-item>
  {{ 'Emily' }}
  {{ 27 | calculate }}
</md-list-item>
<md-list-item>
  {{ 'Dan' }}
  {{ 27 | calculate }}
</md-list-item>

Angular is going to evaluate {{ 27 | calculate }} twice! The optimization that the framework will perform is that it won’t re-evaluate the expressions on the next tick of the change detection mechanism. This means that it’ll cache the result for the last value for a specific place in the rendered (dynamic) component tree.

On the other hand, in functional programming for pure functions, we can apply an optimization called memoization which Wikipedia defines as:

In computing, memoization or memoization is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls and returning the cached result when the same inputs occur again.

In contrast to the caching that Angular performs, memoization uses a global cache used for all the invocations of given function. This means that in the context of the example above if we apply memoization to the fibonacci function, although {{ 27 | calculate }} will be evaluated twice, fibonacci(27) will be invoked only once, the next time we’ll directly get the memoized result!

We can apply memoization to fibonacci using lodash.memoize:

const memoize = require('lodash.memoize');

const fibonacci = memoize((num: number): number => {
  if (num === 1 || num === 2) return 1;
  return fibonacci(num - 1) + fibonacci(num - 2);
});

Now lets see how this affects our initial rendering:

Not bad! By performing this optimization, we reduced the initial rendering with 30%!

Memoization, Pure Pipes, On Push and Referential Transparency

As we saw from the example above, we can think of pure pipes as pure functions. On the other hand, the optimization that Angular performs for pure pipes differs quite a lot compared to memoization. Angular treats expressions formed by pure pipes as referentially transparent expressions. Here’s definition of the property of referential transparency:

If all functions involved in the expression are pure functions, then the expression is referentially transparent. Also, some impure functions can be included in the expression if their values are discarded and their side effects are insignificant.

To what degree we consider the side effect performed by a pure pipe “insignificant” depends on our own judgment. In case we decide that the side effect that our pure pipe performs is significant, we can declare the pipe as impure by setting the pure property of the object literal we pass to the @Pipe decorator to false.

An interesting question to ask is, can we apply memoization to all pure pipes in order to get the performance benefit from the latest optimization we did? Here we can think of two cases:

  • When the arguments of the pipe are primitive values.
  • When the arguments of the pipe are non-primitive values.

For primitive values, it makes sense to apply memoization since we can create a cache which maps the pipe’s arguments to their results. Of course, this is only applicable in case the pure pipe performs no side effects or they are insignificant enough.

On the other hand, if the pipe has non-primitive arguments the situation can get trickier. In such case, it’ll be quite inconvenient to cache the result for the function based on its arguments’ references. Very often two instances of a data structure which have different references can hold the same data. Should we return the cached result in such case or not? Probably we should. But how can we efficiently determine if the items in the two collections are the same? Maybe hashing? This is a topic of another discussion and there is a lot of research in this direction.

For components, we can think similarly. We can abstract them as functions and consider their inputs as arguments, and the UI that they render as the function’s result. We think of the Angular templates as expressions and the components with OnPush change detection used in these templates as referentially transparent sub-expressions.

That’s how we found some patterns which are already well described by computer science in the face of pure functions, referential transparency, and memoization.

Conclusion

This blog post was the second from the series “Faster Angular Applications”.

In this part, we improved the operations for adding and removing employees from the lists by encapsulating the business computation inside of a pure pipe. After that we explained what optimizations Angular performs on pure pipes, treating the expressions that they form as referentially transparent.

As the next step, to optimize the initial rendering of our application, we used memoization.

Finally, we explained the theoretical foundation behind these APIs. It’s definitely worth it to invest time in computer science to understand the underlying concepts behind everything we use on a daily basis. This way we can find agnostic patterns across technologies and reuse them in our projects.

In conclusion, we can say that there’s no silver bullet for optimizing the runtime performance of our Angular applications. There are several techniques we can apply in response to our deep understanding of:

  • The component structure of our applications.
  • The business data that our application processes and visualizes.

On top of that, to conclude if the optimizations we do are meaningful, we should make application-specific benchmarks.