JS: improve performance of Hashtable-based collections

This commit is contained in:
Alexey Andreev
2016-05-13 12:13:47 +03:00
parent f0ee52faaf
commit 81bdd6b4bb
4 changed files with 287 additions and 271 deletions
+4
View File
@@ -9,6 +9,10 @@
### Compiler
### JS
- Improve performance of maps and sets
#### Analysis & diagnostics
- Combination of 'open' and 'override' is no longer a warning
@@ -78,4 +78,8 @@ public final class StandardClassesTest extends SingleFileTranslationTest {
public void ignore_testThrowableImpl() throws Exception {
checkFooBoxIsOk();
}
public void testMutableMapRemoveWithCollision() throws Exception {
checkFooBoxIsOk();
}
}
+257 -271
View File
@@ -272,241 +272,247 @@
* @param {(function(Key, Key): boolean)=} equalityFunctionParam
* @template Key, Value
*/
var Hashtable = function (hashingFunctionParam, equalityFunctionParam) {
var that = this;
var buckets = [];
var bucketsByHash = {};
function Hashtable(hashingFunctionParam, equalityFunctionParam) {
this.buckets = [];
this.bucketsByHash = {};
var hashingFunction = (typeof hashingFunctionParam == FUNCTION) ? hashingFunctionParam : hashObject;
var equalityFunction = (typeof equalityFunctionParam == FUNCTION) ? equalityFunctionParam : null;
this.hashingFunction = (typeof hashingFunctionParam == FUNCTION) ? hashingFunctionParam : hashObject;
this.equalityFunction = (typeof equalityFunctionParam == FUNCTION) ? equalityFunctionParam : null;
this._size = 0;
}
this.put_wn2jw4$ = function (key, value) {
var hash = hashingFunction(key), bucket, bucketEntry, oldValue = null;
Hashtable.prototype.put_wn2jw4$ = function (key, value) {
var hash = this.hashingFunction(key), bucket, bucketEntry, oldValue = null;
// Check if a bucket exists for the bucket key
bucket = getBucketForHash(bucketsByHash, hash);
if (bucket) {
// Check this bucket to see if it already contains this key
bucketEntry = bucket.getEntryForKey(key);
if (bucketEntry) {
// This bucket entry is the current mapping of key to value, so replace old value and we're done.
oldValue = bucketEntry[1];
bucketEntry[1] = value;
}
else {
// The bucket does not contain an entry for this key, so add one
bucket.addEntry(key, value);
}
// Check if a bucket exists for the bucket key
bucket = getBucketForHash(this.bucketsByHash, hash);
if (bucket) {
// Check this bucket to see if it already contains this key
bucketEntry = bucket.getEntryForKey(key);
if (bucketEntry) {
// This bucket entry is the current mapping of key to value, so replace old value and we're done.
oldValue = bucketEntry[1];
bucketEntry[1] = value;
}
else {
// No bucket exists for the key, so create one and put our key/value mapping in
bucket = new Bucket(hash, key, value, equalityFunction);
buckets[buckets.length] = bucket;
bucketsByHash[hash] = bucket;
// The bucket does not contain an entry for this key, so add one
bucket.addEntry(key, value);
this._size++;
}
return oldValue;
};
}
else {
// No bucket exists for the key, so create one and put our key/value mapping in
bucket = new Bucket(hash, key, value, this.equalityFunction);
this.buckets[this.buckets.length] = bucket;
this.bucketsByHash[hash] = bucket;
this._size++;
}
return oldValue;
};
this.get_za3rmp$ = function (key) {
var hash = hashingFunction(key);
Hashtable.prototype.get_za3rmp$ = function (key) {
var hash = this.hashingFunction(key);
// Check if a bucket exists for the bucket key
var bucket = getBucketForHash(bucketsByHash, hash);
if (bucket) {
// Check this bucket to see if it contains this key
var bucketEntry = bucket.getEntryForKey(key);
if (bucketEntry) {
// This bucket entry is the current mapping of key to value, so return the value.
return bucketEntry[1];
}
// Check if a bucket exists for the bucket key
var bucket = getBucketForHash(this.bucketsByHash, hash);
if (bucket) {
// Check this bucket to see if it contains this key
var bucketEntry = bucket.getEntryForKey(key);
if (bucketEntry) {
// This bucket entry is the current mapping of key to value, so return the value.
return bucketEntry[1];
}
return null;
};
}
return null;
};
this.containsKey_za3rmp$ = function (key) {
var bucketKey = hashingFunction(key);
Hashtable.prototype.containsKey_za3rmp$ = function (key) {
var bucketKey = this.hashingFunction(key);
// Check if a bucket exists for the bucket key
var bucket = getBucketForHash(bucketsByHash, bucketKey);
// Check if a bucket exists for the bucket key
var bucket = getBucketForHash(this.bucketsByHash, bucketKey);
return bucket ? bucket.containsKey_za3rmp$(key) : false;
};
return bucket ? bucket.containsKey_za3rmp$(key) : false;
};
this.containsValue_za3rmp$ = function (value) {
var i = buckets.length;
Hashtable.prototype.containsValue_za3rmp$ = function (value) {
var i = this.buckets.length;
while (i--) {
if (this.buckets[i].containsValue_za3rmp$(value)) {
return true;
}
}
return false;
};
Hashtable.prototype.clear = function () {
this.buckets.length = 0;
this.bucketsByHash = {};
this._size = 0;
};
Hashtable.prototype.isEmpty = function () {
return !this.buckets.length;
};
Hashtable.prototype._keys = function () {
var aggregated = [], i = this.buckets.length;
while (i--) {
this.buckets[i].keys(aggregated);
}
return aggregated;
};
Hashtable.prototype._values = function () {
var aggregated = [], i = this.buckets.length;
while (i--) {
this.buckets[i].values(aggregated);
}
return aggregated;
};
Hashtable.prototype._entries = function () {
var aggregated = [], i = this.buckets.length;
while (i--) {
this.buckets[i].getEntries(aggregated);
}
return aggregated;
};
Object.defineProperty(Hashtable.prototype, "values", {
get: function () {
var values = this._values();
var i = values.length;
var result = new Kotlin.ArrayList();
while (i--) {
if (buckets[i].containsValue_za3rmp$(value)) {
return true;
result.add_za3rmp$(values[i]);
}
return result;
},
configurable: true
});
Hashtable.prototype.remove_za3rmp$ = function (key) {
var hash = this.hashingFunction(key), bucketIndex, oldValue = null, result = null;
// Check if a bucket exists for the bucket key
var bucket = getBucketForHash(this.bucketsByHash, hash);
if (bucket) {
// Remove entry from this bucket for this key
result = bucket.removeEntryForKey(key);
if (result !== null) {
this._size--;
oldValue = result[1];
// Entry was removed, so check if bucket is empty
if (!bucket.entries.length) {
// Bucket is empty, so remove it from the bucket collections
bucketIndex = searchBuckets(this.buckets, hash);
arrayRemoveAt(this.buckets, bucketIndex);
delete this.bucketsByHash[hash];
}
}
return false;
};
}
return oldValue;
};
this.clear = function () {
buckets.length = 0;
bucketsByHash = {};
};
Object.defineProperty(Hashtable.prototype, "size", {
get: function () {
return this._size;
}
});
this.isEmpty = function () {
return !buckets.length;
};
Hashtable.prototype.each = function (callback) {
var entries = this._entries(), i = entries.length, entry;
while (i--) {
entry = entries[i];
callback(entry[0], entry[1]);
}
};
var createBucketAggregator = function (bucketFuncName) {
return function () {
var aggregated = [], i = buckets.length;
while (i--) {
buckets[i][bucketFuncName](aggregated);
}
return aggregated;
};
};
Hashtable.prototype.putAll_r12sna$ = hashMapPutAll;
this._keys = createBucketAggregator("keys");
this._values = createBucketAggregator("values");
this._entries = createBucketAggregator("getEntries");
Hashtable.prototype.clone = function () {
var clone = new Hashtable(this.hashingFunction, this.equalityFunction);
clone.putAll_r12sna$(this);
return clone;
};
Object.defineProperty(this, "values", {
get: function () {
var values = this._values();
var i = values.length;
var result = new Kotlin.ArrayList();
while (i--) {
result.add_za3rmp$(values[i]);
}
return result;
},
configurable: true
});
Object.defineProperty(Hashtable.prototype, "keys", {
get: function () {
var res = new Kotlin.ComplexHashSet();
var keys = this._keys();
var i = keys.length;
while (i--) {
res.add_za3rmp$(keys[i]);
}
return res;
},
configurable: true
});
Object.defineProperty(Hashtable.prototype, "entries", {
get: function () {
var result = new Kotlin.ComplexHashSet();
var entries = this._entries();
var i = entries.length;
while (i--) {
var entry = entries[i];
result.add_za3rmp$(new Entry(entry[0], entry[1]));
}
this.remove_za3rmp$ = function (key) {
var hash = hashingFunction(key), bucketIndex, oldValue = null, result = null;
return result;
},
configurable: true
});
// Check if a bucket exists for the bucket key
var bucket = getBucketForHash(bucketsByHash, hash);
Hashtable.prototype.hashCode = function() {
var h = 0;
var entries = this._entries();
var i = entries.length;
while (i--) {
var entry = entries[i];
h += mapEntryHashCode(entry[0], entry[1]);
}
return h;
};
if (bucket) {
// Remove entry from this bucket for this key
result = bucket.removeEntryForKey(key);
if (result !== null) {
oldValue = result[1];
Hashtable.prototype.equals_za3rmp$ = function(o) {
if (o == null || this.size !== o.size) return false;
// Entry was removed, so check if bucket is empty
if (!bucket.entries.length) {
// Bucket is empty, so remove it from the bucket collections
bucketIndex = searchBuckets(buckets, hash);
arrayRemoveAt(buckets, bucketIndex);
delete bucketsByHash[hash];
}
}
var entries = this._entries();
var i = entries.length;
while (i--) {
var entry = entries[i];
var key = entry[0];
var value = entry[1];
if (value == null) {
if (!(o.get_za3rmp$(key) == null && o.contains_za3rmp$(key))) return false;
}
return oldValue;
};
Object.defineProperty(this, "size", {
get: function () {
var total = 0, i = buckets.length;
while (i--) {
total += buckets[i].entries.length;
}
return total;
else {
if (!Kotlin.equals(value, o.get_za3rmp$(key))) return false;
}
});
}
return true;
};
this.each = function (callback) {
var entries = that._entries(), i = entries.length, entry;
while (i--) {
entry = entries[i];
callback(entry[0], entry[1]);
}
};
/**
* @param {Hashtable.<Key, Value>} hashtable
*/
this.putAll_r12sna$ = hashMapPutAll;
this.clone = function () {
var clone = new Hashtable(hashingFunctionParam, equalityFunctionParam);
clone.putAll_r12sna$(that);
return clone;
};
Object.defineProperty(this, "keys", {
get: function () {
var res = new Kotlin.ComplexHashSet();
var keys = this._keys();
var i = keys.length;
while (i--) {
res.add_za3rmp$(keys[i]);
}
return res;
},
configurable: true
});
Object.defineProperty(this, "entries", {
get: function () {
var result = new Kotlin.ComplexHashSet();
var entries = this._entries();
var i = entries.length;
while (i--) {
var entry = entries[i];
result.add_za3rmp$(new Entry(entry[0], entry[1]));
}
return result;
},
configurable: true
});
this.hashCode = function() {
var h = 0;
var entries = this._entries();
var i = entries.length;
while (i--) {
var entry = entries[i];
h += mapEntryHashCode(entry[0], entry[1]);
}
return h;
};
this.equals_za3rmp$ = function(o) {
if (o == null || this.size !== o.size) return false;
var entries = this._entries();
var i = entries.length;
while (i--) {
var entry = entries[i];
var key = entry[0];
var value = entry[1];
if (value == null) {
if (!(o.get_za3rmp$(key) == null && o.contains_za3rmp$(key))) return false;
}
else {
if (!Kotlin.equals(value, o.get_za3rmp$(key))) return false;
}
}
return true;
};
this.toString = function() {
var entries = this._entries();
var length = entries.length;
if (length === 0) return "{}";
var builder = "{";
for (var i = 0;;) {
var entry = entries[i];
var key = entry[0];
var value = entry[1];
builder +=
(key === this ? "(this Map)" : Kotlin.toString(key)) +
"=" +
(value === this ? "(this Map)" : Kotlin.toString(value));
if (++i >= length) return builder + "}";
builder += ", "
}
};
Hashtable.prototype.toString = function() {
var entries = this._entries();
var length = entries.length;
if (length === 0) return "{}";
var builder = "{";
for (var i = 0;;) {
var entry = entries[i];
var key = entry[0];
var value = entry[1];
builder +=
(key === this ? "(this Map)" : Kotlin.toString(key)) +
"=" +
(value === this ? "(this Map)" : Kotlin.toString(value));
if (++i >= length) return builder + "}";
builder += ", "
}
};
Kotlin.HashTable = Hashtable;
@@ -515,7 +521,7 @@
lazyInitClasses.HashMap = Kotlin.createClass(
function () {
return [Kotlin.modules['builtins'].kotlin.collections.MutableMap];
return [Kotlin.HashTable, Kotlin.modules['builtins'].kotlin.collections.MutableMap];
},
function () {
Kotlin.HashTable.call(this);
@@ -766,75 +772,55 @@
convertKeyToKeyType: convertKeyToBoolean
});
function LinkedHashMap() {
Kotlin.ComplexHashMap.call(this);
this.orderedKeys = [];
this.super_put_wn2jw4$ = this.put_wn2jw4$;
this.put_wn2jw4$ = function(key, value) {
if (!this.containsKey_za3rmp$(key)) {
this.orderedKeys.push(key);
}
return this.super_put_wn2jw4$(key, value);
};
this.super_remove_za3rmp$ = this.remove_za3rmp$;
this.remove_za3rmp$ = function(key) {
var i = this.orderedKeys.indexOf(key);
if (i != -1) {
this.orderedKeys.splice(i, 1);
}
return this.super_remove_za3rmp$(key);
};
this.super_clear = this.clear;
this.clear = function() {
this.super_clear();
this.orderedKeys = [];
};
Object.defineProperty(this, "keys", {
get: function () {
// TODO return special Set which unsupported adding
var set = new Kotlin.LinkedHashSet();
set.map = this;
return set;
}
});
Object.defineProperty(this, "entries", {
get: function () {
var result = new Kotlin.ArrayList();
for (var i = 0, c = this.orderedKeys, l = c.length; i < l; i++) {
result.add_za3rmp$(this.get_za3rmp$(c[i]));
}
return result;
}
});
Object.defineProperty(this, "entries", {
get: function () {
var set = new Kotlin.LinkedHashSet();
for (var i = 0, c = this.orderedKeys, l = c.length; i < l; i++) {
set.add_za3rmp$(new Entry(c[i], this.get_za3rmp$(c[i])));
}
return set;
}
});
}
lazyInitClasses.LinkedHashMap = Kotlin.createClass(
function () {
return [Kotlin.ComplexHashMap];
},
/** @constructs */
function () {
LinkedHashMap.call(this);
Kotlin.ComplexHashMap.call(this);
this.orderedKeys = [];
},
/** @lends {Kotlin.LinkedHashMap.prototype} */
{
put_wn2jw4$: function (key, value) {
if (!this.containsKey_za3rmp$(key)) {
this.orderedKeys.push(key);
}
return Kotlin.ComplexHashMap.prototype.put_wn2jw4$.call(this, key, value);
},
remove_za3rmp$: function (key) {
var i = this.orderedKeys.indexOf(key);
if (i != -1) {
this.orderedKeys.splice(i, 1);
}
return Kotlin.ComplexHashMap.prototype.remove_za3rmp$.call(this, key);
},
clear: function () {
Kotlin.ComplexHashMap.prototype.clear.call(this);
this.orderedKeys = [];
},
keys: {
get: function () {
// TODO return special Set which unsupported adding
var set = new Kotlin.LinkedHashSet();
set.map = this;
return set;
}
},
entries: {
get: function () {
var set = new Kotlin.LinkedHashSet();
for (var i = 0, c = this.orderedKeys, l = c.length; i < l; i++) {
set.add_za3rmp$(new Entry(c[i], this.get_za3rmp$(c[i])));
}
return set;
}
}
}
);
@@ -0,0 +1,22 @@
package foo
class A {
override fun hashCode() = 23456
}
fun box(): String {
val x = A()
val y = A()
val map = mutableMapOf<A, Int>()
map[x] = 1
assertEquals(1, map.size)
map.remove(y)
assertEquals(1, map.size)
map.remove(x)
assertEquals(0, map.size)
return "OK"
}