Object Pooling Techniques

JavaScript performance comparison

Test case created by Andrew Petersen

Preparation code


      
      <script>
Benchmark.prototype.setup = function() {
  // "threaded list" implementation
  function PoolES2015 (ctor, initialCount) {
    const NEXT = Symbol('next');
    const POOLED = Symbol('pooled');
    const RETAINED = Symbol('retained');
  
    let first = null;
    let prev = null;
  
    this.POOLED = POOLED;
    this.RETAINED = RETAINED;
    this.count = initialCount;
    this.freeCount = 0;
  
    const allocate = () => {
      first = new ctor;
      prev = first;
  
      for (let i = 0; i < this.count; i++) {
        let next = new ctor;
        prev[NEXT] = next;
        prev[POOLED] = true;
        prev[RETAINED] = false;
        prev = next;
        this.freeCount += 1;
      }
  
      prev[NEXT] = null;
    }
  
    this.retain = () => {
      if (first === null) {
        this.count = this.count * this.count;
        allocate();
      }
  
      const free = first;
      first = free[NEXT];
      this.freeCount -= 1;
      free[RETAINED] = true;
      return free;
    }
  
    this.release = (obj) => {
      obj[RETAINED] = false;
      obj[NEXT] = first;
      first = obj;
      this.freeCount += 1;
    }
  
    allocate();
  }
  
  function PoolES5 (ctor, initialCount) {
    this.NEXT = '__next__';
    this.POOLED = '__pooled__';
    this.RETAINED = '__retained__';
  
    this.first = null;
    this.prev = null;
  
    this.count = initialCount;
    this.freeCount = 0;
  
    this.ctor = ctor;
  
    this.allocate();
  }
  
  PoolES5.prototype.allocate = function () {
    this.first = new this.ctor;
    this.prev = this.first;
  
    for (var i = 0; i < this.count; i++) {
      var next = new this.ctor;
      this.prev[this.NEXT] = next;
      this.prev[this.POOLED] = true;
      this.prev[this.RETAINED] = false;
      this.prev = next;
      this.freeCount += 1;
    }
  
    this.prev[this.NEXT] = null;
  }
  
  PoolES5.prototype.retain = function () {
    if (this.first === null) {
      this.count = this.count * this.count;
      this.allocate();
    }
  
    var free = this.first;
    this.first = free[this.NEXT];
    this.freeCount -= 1;
    free[this.RETAINED] = true;
    return free;
  }
  
  PoolES5.prototype.release = function (obj) {
    obj[this.RETAINED] = false;
    obj[this.NEXT] = this.first;
    this.first = obj;
    this.freeCount += 1;
  }
  
  
  // FROM https://jsperf.com/pooling-test/8
  
  var setupPool  =  function(func, initialPoolSize) {
        if (arguments.length<2) { 
            throw ('setupPool takes two arguments');    }
        func.pool                = []        ;
        func.poolSize            = 0         ;
        func.maxPoolSize         = initialPoolSize ;
        func.pnew                = pnew      ;
        func.prototype.pdispose  = pdispose  ; 
        // pre-fill the pool.
        for (var i=0; i<initialPoolSize; i++) {
            (new func()).pdispose();
         }
    };
    
    var pnew     = function() {
        var pnewObj  = null     ; 
        if (this.poolSize>0 ) {
               // the pool contains objects -> grab one
               this.poolSize--  ;
               pnewObj = this.pool[this.poolSize];
               this.pool[this.poolSize] = null   ; // **
        } else {
               // the pool is empty -> create new object
               pnewObj = new this() ;
        }
        // return initialized object.
        return this.apply(pnewObj, arguments);
    };
    
    var pdispose   = function() {
        var thisCttr = this.constructor             ;
        // throw the object back in the pool 
        thisCttr.pool[thisCttr.poolSize++] = this ;
        // update maxPoolSize
        thisCttr.maxPoolSize = Math.max(thisCttr.maxPoolSize,
                                                 thisCttr.poolSize); 
    };
  
  
  function reducer (state, action) {
    state = state || 0;
    var payload = action.payload;
    var type = action.type;
    switch (action.type) {
      case 'INC': {
        state += payload;
        return state;
      }
  
      case 'DEC': {
        state -= payload;
        return state;
      }
  
      default: return state;
    }
  }
  
  var PREALLOC_AMOUNT = 100;
  function ObjMaker () { return this; }
  setupPool(ObjMaker, PREALLOC_AMOUNT);
  var poolES2015 = new PoolES2015(Object, PREALLOC_AMOUNT);
  var poolES5 = new PoolES5(Object, PREALLOC_AMOUNT);
  var ACTION_COUNT = 100;
  

};
</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
0: Plain Inline
var state = null;
var action1 = { type: 'INC', payload: 1 };
state = reducer(state, action1);
var action2 = { type: 'DEC', payload: 1 };
state = reducer(state, action2);
pending…
1: Plain expando
var state = null;
var action1 = {}
action1.type = 'INC';
action1.payload = 1;
state = reducer(state, action1);
var action2 = {}
action2.type = 'DEC';
action2.payload = 1;
state = reducer(state, action2);
pending…
2: pnew ?????
var state = null;
var action1 = ObjMaker.pnew();
action1.type = 'INC';
action1.payload = 1;
state = reducer(state, action1);
action1.pdispose();
var action2 = ObjMaker.pnew();
action2.type = 'DEC';
action2.payload = 1;
state = reducer(state, action2);
action2.pdispose();
pending…
3: free list ES2015
var state = null;
var action1 = poolES2015.retain();
action1.type = 'INC';
action1.payload = 1;
state = reducer(state, action1);
poolES2015.release(action1);
var action2 = poolES2015.retain();
action2.type = 'DEC';
action2.payload = 1;
state = reducer(state, action2);
poolES2015.release(action2);
pending…
4: free list ES5
var state = null;
var action1 = poolES5.retain();
action1.type = 'INC';
action1.payload = 1;
state = reducer(state, action1);
poolES5.release(action1);
var action2 = poolES5.retain();
action2.type = 'INC';
action2.payload = 1;
state = reducer(state, action2);
poolES5.release(action2);
pending…
5: free list ES5, clean object
var state = null;
var action1 = poolES5.retain();
action1.type = 'INC';
action1.payload = 1;
state = reducer(state, action1);
delete action1.type;
delete action1.payload;
poolES5.release(action1);
var action2 = poolES5.retain();
action2.type = 'INC';
action2.payload = 1;
state = reducer(state, action2);
delete action2.type;
delete action2.payload;
poolES5.release(action2);
pending…

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

0 Comments