generator-vs-closure

JavaScript performance comparison

Test case created by drpicox

Preparation code

<script>
var count = 1000;

function* makeNativeIterator() {
  for (var i = 0; i < count; i+= 1) {
    yield i;
  }
  return count;
}

function makeCustomIterator() {
  var i = -1;
  return {
    next() {
      i += 1;
      if (i >= count) return { value: count, done: true };
      return { value: i, done: false };
    },
  };
}


function makeTrickIterator() {
  var i = -1;
  var result = { value: i, done: false };
  return {
    next() {
      i += 1;
      if (i >= count) return { value: count, done: true };
      result.value = i;
      return result;
    },
  };
}

function makeClosureProducer() {
  var i = -1;
  return function() {
    i += 1;
    if (i > count) return null;
    return i;
  };
}

function makeNiceProducer() {
  var i = 0;
  return function() {
    for (; i <= count; i+= 1) {
      
    }
    i += 1;
    if (i > count) return null;
    return i;
  };
}

function iterateCallback(cb) {
  for (var i = 0; i < count; i+= 1) {
    cb(i);
  }
  cb(count);
}

function iterateNamedCallback({ cb }) {
  for (var i = 0; i < count; i+= 1) {
    cb(i);
  }
  cb(count);
}

function iterateStoppableCallback(cb) {
  var isStopped = false;
  var stop = () => isStopped = true;
  for (var i = 0; i < count && !isStopped; i+= 1) {
    cb(i, stop);
  }
  cb(count, stop);
}
class OOIterator {
  constructor() {
    this.i = 0;
  }
  isDone() {
    return this.i >= count;
  }
  next() {
    var result = this.i;
    this.i += 1;
    return result;
  }
}

var privates = new WeakMap();
class OOPrivateIterator {
  constructor() {
    privates.set(this, 0);
  }
  isDone() {
    return privates.get(this) >= count;
  }
  next() {
    var result = privates.get(this);
    privates.set(this, result + 1);
    return result;
  }
}

function makeIterator() {
  var i = 0;
  return {
    isDone() {
      return i >= count;
    },
    next() {
      var result = i;
      i += 1;
      return result;
    },
  };
}

function generateArray() {
  var result = [];
  for (var i = 0; i <= count; i+= 1) {
    result.push(i);
  }
  return result;
}

class ManualConversion {
  constructor() {
    this._state = 0;
  }

  next() {
    while(true) {
      switch(this._state) {
        case 0:
          this.i = 0;
          this._state = 1;
          break;
        case 1:
          if (this.i < count) this._state = 2;
          else this._state = 4;
          break;
        case 2:
          this._state = 3;
          return this.i;
        case 3:
          this.i += 1;
          this._state = 1;
          break;
        case 4:
          return null;
      }
    }
  }
}

class TweakManualConversion {
  constructor() {
    this._state = 0;
  }

  next() {
    switch (this._state) {
      case 0:
        this.i = 0;
        if (this.i < count) {
          this._state = 3;
          return this.i;
        } else {
          this._state = 4;
          return null;
        }
      case 3:
        this.i += 1;
        if (this.i < count) {
          this._state = 3;
          return this.i;
        } else {
          this._state = 4;
          return null;
        }
      case 4:
        return null;
    }
  }
}

function TweakNextManualConversion() {
  var _state = 0;
  var i;

  return function() {
    switch (_state) {
      case 0:
        i = 0;
        if (i < count) {
          _state = 3;
          return i;
        } else {
          _state = 4;
          return null;
        }
      case 3:
        i += 1;
        if (i < count) {
          _state = 3;
          return i;
        } else {
          _state = 4;
          return null;
        }
      case 4:
        return null;
    }
  };
}

class TweakIfConversion {
  constructor() {
    this._state = 0;
  }

  next() {
    if (this._state === 0) {
        this.i = 0;
        if (this.i < count) {
          this._state = 3;
          return this.i;
        } else {
          this._state = 4;
          return null;
        }
    } else if (this._state === 3) {
        this.i += 1;
        if (this.i < count) {
          this._state = 3;
          return this.i;
        } else {
          this._state = 4;
          return null;
        }
    } 
    return null;
  }
}
</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
function*
let sum = 0;
const it = makeNativeIterator();
let result = it.next();
while (!result.done) {
  sum += result.value;
  result = it.next();
}
return sum;
pending…
emulate*
let sum = 0;
const it = makeCustomIterator();
let result = it.next();
while (!result.done) {
  sum += result.value;
  result = it.next();
}
return sum;
pending…
closure
let sum = 0;
const next = makeClosureProducer();
let result = next();
while (result !== null) {
  sum += result;
  result = next();
}
return sum;
pending…
callback
let sum = 0;
iterateCallback(function(value) {
  sum += value;
});
return sum;
pending…
named cb
let sum = 0;
iterateNamedCallback({cb: function(value) {
  sum += value;
}});
return sum;
pending…
stoppable cb
let sum = 0;
iterateStoppableCallback(function(value) {
  sum += value;
});
return sum;
pending…
trick*
let sum = 0;
const it = makeTrickIterator();
let result = it.next();
while (!result.done) {
  sum += result.value;
  result = it.next();
}
return sum;
pending…
trick* 2
let sum = 0;
const next = makeTrickIterator().next;
let result = next();
while (!result.done) {
  sum += result.value;
  result = next();
}
return sum;
pending…
trick* 3
let sum = 0;
const next = makeTrickIterator().next;
let {done, value} = next();
while (!done) {
  sum += value;
  ({done, value} = next());
}
return sum;
pending…
oo iterator
let sum = 0;
let it = new OOIterator();
while (!it.isDone()) {
  sum += it.next();  
}
return sum;
pending…
oo iterator private
let sum = 0;
let it = new OOPrivateIterator();
while (!it.isDone()) {
  sum += it.next();  
}
return sum;
pending…
make Iterator
let sum = 0;
let it = makeIterator();
while (!it.isDone()) {
  sum += it.next();  
}
return sum;
pending…
for
let sum = 0;
for (let i = 0; i <= count; i+= 1) {
  sum += i;
}
return sum;
pending…
array
let sum = 0;
let array = generateArray();
for (let i = 0; i < array.length; i+= 1) {
  sum += array[i];
}
return sum;
pending…
array.reduce
let array = generateArray();
let sum = array.reduce((s, i) => s+i, 0);
return sum;
pending…
array.forEach
let sum = 0;
let array = generateArray();
array.forEach(i => sum += i);
return sum;
pending…
manual
let sum = 0;
let it = new ManualConversion();
let current = it.next();
while (current !== null) {
  sum += current;
  current = it.next();
}
return sum;
pending…
next manual tweak
let sum = 0;
let next = TweakNextManualConversion();
let current = next();
while (current !== null) {
  sum += current;
  current = next();
}
return sum;
pending…
manual tweak
let sum = 0;
let it = new TweakManualConversion();
let current = it.next();
while (current !== null) {
  sum += current;
  current = it.next();
}
return sum;
pending…
TweakIfConversion
let sum = 0;
let it = new TweakIfConversion();
let current = it.next();
while (current !== null) {
  sum += current;
  current = it.next();
}
return sum;
pending…

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

Compare results of other browsers

0 Comments