Skip to content

Commit

Permalink
improve HashSet performance
Browse files Browse the repository at this point in the history
Signed-off-by: Eric Vergnaud <[email protected]>
  • Loading branch information
ericvergnaud committed Mar 11, 2024
1 parent 7d4ad89 commit 1f5e64b
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 31 deletions.
2 changes: 1 addition & 1 deletion runtime/JavaScript/src/antlr4/atn/ATNConfigSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export default class ATNConfigSet {
if (config.reachesIntoOuterContext > 0) {
this.dipsIntoOuterContext = true;
}
const existing = this.configLookup.add(config);
const existing = this.configLookup.getOrAdd(config);
if (existing === config) {
this.cachedHashCode = -1;
this.configs.push(config); // track order here
Expand Down
4 changes: 2 additions & 2 deletions runtime/JavaScript/src/antlr4/atn/ParserATNSimulator.js
Original file line number Diff line number Diff line change
Expand Up @@ -1287,7 +1287,7 @@ export default class ParserATNSimulator extends ATNSimulator {
}

c.reachesIntoOuterContext += 1;
if (closureBusy.add(c)!==c) {
if (closureBusy.getOrAdd(c)!==c) {
// avoid infinite recursion for right-recursive rules
continue;
}
Expand All @@ -1297,7 +1297,7 @@ export default class ParserATNSimulator extends ATNSimulator {
console.log("dips into outer ctx: " + c);
}
} else {
if (!t.isEpsilon && closureBusy.add(c)!==c){
if (!t.isEpsilon && closureBusy.getOrAdd(c)!==c){
// avoid infinite recursion for EOF* and EOF+
continue;
}
Expand Down
105 changes: 77 additions & 28 deletions runtime/JavaScript/src/antlr4/misc/HashSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,108 @@ import standardHashCodeFunction from "../utils/standardHashCodeFunction.js";
import standardEqualsFunction from "../utils/standardEqualsFunction.js";
import arrayToString from "../utils/arrayToString.js";

const HASH_KEY_PREFIX = "h-";
const DEFAULT_LOAD_FACTOR = 0.75;
const INITIAL_CAPACITY = 16

export default class HashSet {

constructor(hashFunction, equalsFunction) {
this.data = {};
this.buckets = new Array(INITIAL_CAPACITY);
this.threshold = Math.floor(INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
this.itemCount = 0;
this.hashFunction = hashFunction || standardHashCodeFunction;
this.equalsFunction = equalsFunction || standardEqualsFunction;
}

add(value) {
const key = HASH_KEY_PREFIX + this.hashFunction(value);
if (key in this.data) {
const values = this.data[key];
for (let i = 0; i < values.length; i++) {
if (this.equalsFunction(value, values[i])) {
return values[i];
}
}
values.push(value);
return value;
} else {
this.data[key] = [value];
get(value) {
if(value == null) {
return value;
}
const bucket = this._getBucket(value)
if (!bucket) {
return null;
}
for (const e of bucket) {
if (this.equalsFunction(e, value)) {
return e;
}
}
return null;
}

has(value) {
return this.get(value) != null;
add(value) {
const existing = this.getOrAdd(value);
return existing === value;
}

get(value) {
const key = HASH_KEY_PREFIX + this.hashFunction(value);
if (key in this.data) {
const values = this.data[key];
for (let i = 0; i < values.length; i++) {
if (this.equalsFunction(value, values[i])) {
return values[i];
}
getOrAdd(value) {
this._expand();
const slot = this._getSlot(value);
let bucket = this.buckets[slot];
if (!bucket) {
bucket = [value];
this.buckets[slot] = bucket;
this.itemCount++;
return value;
}
for (const existing of bucket) {
if (this.equalsFunction(existing, value)) {
return existing;
}
}
return null;
bucket.push(value);
this.itemCount++;
return value;

}

has(value) {
return this.get(value) != null;
}


values() {
return Object.keys(this.data).filter(key => key.startsWith(HASH_KEY_PREFIX)).flatMap(key => this.data[key], this);
return this.buckets.filter(b => b != null).flat(1);
}

toString() {
return arrayToString(this.values());
}

get length() {
return Object.keys(this.data).filter(key => key.startsWith(HASH_KEY_PREFIX)).map(key => this.data[key].length, this).reduce((accum, item) => accum + item, 0);
return this.itemCount;
}

_getSlot(value) {
const hash = this.hashFunction(value);
return hash & this.buckets.length - 1;
}
_getBucket(value) {
return this.buckets[this._getSlot(value)];
}

_expand() {
if (this.itemCount <= this.threshold) {
return;
}
const old_buckets = this.buckets;
const newCapacity = this.buckets.length * 2;
this.buckets = new Array(newCapacity);
this.threshold = Math.floor(newCapacity * DEFAULT_LOAD_FACTOR);
for (const bucket of old_buckets) {
if (!bucket) {
continue;
}
for (const o of bucket) {
const slot = this._getSlot(o);
let newBucket = this.buckets[slot];
if (!newBucket) {
newBucket = [];
this.buckets[slot] = newBucket;
}
newBucket.push(o);
}
}

}
}

0 comments on commit 1f5e64b

Please sign in to comment.