Skip to content
This repository has been archived by the owner on Apr 20, 2018. It is now read-only.

RxJS version 2.3.25

Compare
Choose a tag to compare
@mattpodwysocki mattpodwysocki released this 06 Feb 20:08
· 1143 commits to master since this release

This release was mainly focused on performance as we continue to push RxJS forward.

In this release, there were the following changes:

  • Performance Upgrades
  • Method Fusion
  • Observable.prototype.pluck Changes
  • Fixes

Performance Upgrades

RxJS has been committed to bringing the best performance possible. Lately we have been making changes to make it even faster while keeping the memory footprint low. To that end, we've been making strides little by little to identify bottlenecks and eliminate them.

We've been doing the following to ensure performance:

  • Add benchmarks versus previous versions
  • Optimize scheduler usage
  • Reducing scope chains
  • Method Fusion
  • Enhance for Engine Optimizations

Add Benchmarks

To measure our progress, we have set up benchmark tests for performance in the tests/perf/operators folder using benchmark.js which compares the previous version of RxJS to the current edge version. This will also give you insight into what changes we are making and how much of a difference it is making.

To give a concrete example, we were able to make the following improvements to operators such as Observable.prototype.map:

Using node.js v0.10.36 on a Surface 2 Pro as your mileage may vary based upon your machine.

λ node map.js
old x 5,471 ops/sec ±5.04% (82 runs sampled)
new x 7,529 ops/sec ±4.61% (87 runs sampled)
Fastest is new

As you'll notice, the operations per second increase is rather dramatic in some cases, especially for map and filter where we reduced method scopes and created classes for both an Observable and Observer.

Optimize Scheduler Usage

Another optimization we made is to use schedulers more efficiently, especially around using scheduleWithState, and scheduleRecursiveWithState which are more efficient than their non-state counterparts of schedule and scheduleRecursive. One such example is how Observable.fromArray was optimized. Previously, we had an index outside the scope of the scheduler which was incremented, and chained scopes can be expensive if you mutate variables. In this case, we are no longer doing that and instead, start with a state of 0 and then incrementing it via the recursive call to self(i + 1).

Observable.fromArray = function (array, scheduler) {
  var len = array.length;
  isScheduler(scheduler) || (scheduler = currentThreadScheduler);
  return new AnonymousObservable(function (observer) {
    return scheduler.scheduleRecursiveWithState(0, function (i, self) {
      if (i < len) {
        observer.onNext(array[i]);
        self(i + 1);
      } else {
        observer.onCompleted();
      }
    });
  });
};

And we're not stopping here to make schedulers more efficient since they are the heart of Rx.

Reducing Scope Chains

If you looked at previous implementations of map, you'll notice there are nested scope chains, especially with mutation of the count variable.

Observable.prototype.map = function (selector, thisArg) {
  var selectorFn = isFunction(selector) ? bindCallback(selector, thisArg, 3) : function () { return selector; },
      source = this;
  return new AnonymousObservable(function (o) {
    var count = 0;
    return source.subscribe(function (value) {
      try {
        var result = selectorFn(value, count++, source);
      } catch (e) {
        return o.onError(e);
      }
      o.onNext(result);
    }, function (e) { o.onError(e); }, function () { o.onCompleted(); });
  }, source);
};

To get around this we created classes for map with an Observable and Observer here. This allows us also future optimizations such as method fusion which we will get into later.

Method Fusion

Another way we could squeeze more performance through the technique of method fusion, meaning the ability to fuse two calls into one. For example, if you have two filter calls in a row, we could easily flatten that into a single filter call, and the same applies to map as well.

Observable.prototype.map = function (selector, thisArg) {
  var selectorFn = isFunction(selector) ? selector : function () { return selector; };
  return this instanceof MapObservable ?
    this.internalMap(selector, thisArg) :
    new MapObservable(this, selectorFn, thisArg);
};

Here we are detecting if there are two map calls in a row, and then can flatten them into a single call:

MapObservable.prototype.internalMap = function (selector, thisArg) {
  var self = this;
  return new MapObservable(
    this.source,
    function (x, i, o) { return selector(self.selector(x, i, o), i, o); }, thisArg)
};

Enhance For Engine Optimizations

One bit more involved is to ensure that we are optimizing for the ways that JavaScript engines work. For example, the Bluebird library team has a good document on optimization killers for V8.

One simple example was to ensure that we were not leaking arguments anywhere. To fix that is simple, simply a call such as the following where instead of calling slice on arguments which can be expensive, we can simply inline the arguments into a new Array with the proper size.

Observable.of = function() {
  var a = new Array(arguments.length), len = a.length;
  for(var i = 0; i < len; i++) { a.push(arguments[i]);   }
  observableOf(null, a);
};

We are looking into the next version of fixing all functions with try/catch and try/finally to ensure that we have faster speeds which will come in the next release.

Observable.prototype.pluck Changes

One request that we've had for some time is having the ability to get nested properties using the Observable.prototype.pluck operator. To that and, we have added the ability to get to nested properties by giving an argument list of properties to pluck. So, for example, we could pull out the event.target.value from the event argument.

var element = document.querySelector('#input');
var source = Rx.Observable.fromEvent(element, 'keyup')
  .pluck('target', 'value');

This is equivalent to using map in this way:

var element = document.querySelector('#input');
var source = Rx.Observable.fromEvent(element, 'keyup')
  .map(function(event) { return event.target.value; });

Why didn't we just allow a single string to pluck the values like some other libraries have done? That's because there's no easy way to break them up, after all, having a dot in the name of a variable is acceptable, thus leading to unpredictable behavior.

var foo = {
  '.bar': {
    '.baz': 42
  }
};

If you try to use a string like target.value it will not work in that case.

Many thanks to @sergi for his help in creating this operator.

Fixes