Ramda _clone perf improvement

Benchmark created on


Setup

function type(val) {
  return val === null
    ? 'Null'
    : val === undefined
      ? 'Undefined'
      : Object.prototype.toString.call(val).slice(8, -1);
}

function _current(value, deep, map) {

  map || (map = new _ObjectMap());

  // this avoids the slower switch with a quick if decision removing some milliseconds in each run.
  if (_isPrimitive(value)) {
    return value;
  }

  var copy = function copy(copiedValue) {
    // Check for circular and same references on the object graph and return its corresponding clone.
    var cachedCopy = map.get(value);

    if (cachedCopy) {
      return cachedCopy;
    }

    map.set(value, copiedValue);

    for (var key in value) {
      if (Object.prototype.hasOwnProperty.call(value, key)) {
        copiedValue[key] = deep ? _current(value[key], true, map) : value[key];
      }
    }
    return copiedValue;
  };

  switch (type(value)) {
    case 'Object':  return copy(Object.create(Object.getPrototypeOf(value)));
    case 'Array':   return copy([]);
    default:        return value;
  }
}

function _next(value, deep, map) {

  map || (map = new _ObjectMap());

  // this avoids the slower switch with a quick if decision removing some milliseconds in each run.
  if (_isPrimitive(value)) {
    return value;
  }

  var copy = function copy(copiedValue) {
    // Check for circular and same references on the object graph and return its corresponding clone.
    var cachedCopy = map.get(value);

    if (cachedCopy) {
      return cachedCopy;
    }

    map.set(value, copiedValue);

    for (var key in value) {
      if (Object.prototype.hasOwnProperty.call(value, key)) {
        copiedValue[key] = deep ? _next(value[key], true, map) : value[key];
      }
    }
    return copiedValue;
  };

  switch (type(value)) {
    case 'Object':  return copy(Object.create(Object.getPrototypeOf(value)));
    case 'Array':   return copy(Array(value.length));
    default:        return value;
  }
}

function _isPrimitive(param) {
  var type = typeof param;
  return param == null || (type != 'object' && type != 'function');
}

function _ObjectMap() {
  this.map = {};
  this.length = 0;
}

_ObjectMap.prototype.set = function(key, value) {
  const hashedKey = this.hash(key);

  let bucket = this.map[hashedKey];
  if (!bucket) {
    this.map[hashedKey] = bucket = [];
  }

  bucket.push([key, value]);
  this.length += 1;
};

_ObjectMap.prototype.hash = function(key) {
  let hashedKey = [];
  for (var value in key) {
    hashedKey.push(Object.prototype.toString.call(key[value]));
  }
  return hashedKey.join();
};

_ObjectMap.prototype.get = function(key) {

  /**
   * depending on the number of objects to be cloned is faster to just iterate over the items in the map just because the hash function is so costly,
   * on my tests this number is 180, anything above that using the hash function is faster.
   */
  if (this.length <= 180) {

    for (const p in this.map) {
      const bucket = this.map[p];

      for (let i = 0; i < bucket.length; i += 1) {
        const element = bucket[i];
        if (element[0] === key) {return element[1];}
      }

    }
    return;
  }

  const hashedKey = this.hash(key);
  const bucket = this.map[hashedKey];

  if (!bucket) {return;}

  for (let i = 0; i < bucket.length; i += 1) {
    const element = bucket[i];
    if (element[0] === key) {return element[1];}
  }

};

var arr = [];
for (var i = 0; i < 1000; i++) {
  var inner = [];
  for (var j = 0; j < 10; j++) {
    inner[j] = j;
  }
  arr[i] = inner;
}

Test runner

Ready to run.

Testing in
TestOps/sec
Current
_current(arr, true)
ready
Next
_next(arr, true)
ready

Revisions

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