Skip to content

Commit

Permalink
Replace string conversion with prime multiplication in hash
Browse files Browse the repository at this point in the history
It's benchmarking about 67% faster without any degradation in uniformity.

Also fixed some tests that were assuming some things about hashing.
  • Loading branch information
lettertwo committed Sep 14, 2021
1 parent a14f211 commit e047e46
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 21 deletions.
22 changes: 9 additions & 13 deletions packages/core/graph/src/AdjacencyList.js
Original file line number Diff line number Diff line change
Expand Up @@ -1099,19 +1099,15 @@ export default class AdjacencyList<TEdgeType: number = 1> {
*
*/
hash(from: NodeId, to: NodeId, type: TEdgeType | NullEdgeType): EdgeHash {
// A crude multiplicative hash, in 3 steps:
// 1. Serialize the args into an integer that reflects the argument order,
// shifting the magnitude of each argument by the sum
// of the significant digits of the following arguments,
// .e.g., `hash(10, 24, 4) => 10244`.
// $FlowFixMe[unsafe-addition]
// $FlowFixMe[incompatible-type]
let hash = '' + from + to + type - 0;
// 2. Mix the upper bits of the integer into the lower bits.
// We do this to increase the likelihood that a change to any
// bit of the input will vary the output widely.
hash = hash32shift(hash);
// 3. Map the hash to a value modulo the edge capacity.
// Each parameter is hashed by mixing its upper bits into its lower bits to
// increase the likelihood that a change to any bit of the input will vary
// the output widely. Then we do a series of prime multiplications and
// additions to combine the hashes into one value.
let hash = 17;
hash = hash * 37 + hash32shift((from: any));
hash = hash * 37 + hash32shift((to: any));
hash = hash * 37 + hash32shift((type: any));
// Finally, we map the hash to a value modulo the edge capacity.
hash %= this.#edges[CAPACITY];
return hash;
}
Expand Down
35 changes: 27 additions & 8 deletions packages/core/graph/test/AdjacencyList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,13 @@ describe('AdjacencyList', () => {
});

it('addEdge should not replace a deleted edge if the edge was already added', () => {
let graph = new AdjacencyList();
// Mock hash fn to generate collisions
// $FlowFixMe[method-unbinding]
let originalHash = AdjacencyList.prototype.hash;
// $FlowFixMe[cannot-write]
graph.hash = () => 1;
AdjacencyList.prototype.hash = () => 1;

let graph = new AdjacencyList();
let n0 = graph.addNode();
let n1 = graph.addNode();
let n2 = graph.addNode();
Expand All @@ -210,26 +213,42 @@ describe('AdjacencyList', () => {
graph.addEdge(n0, n1, 1);
assert(!graph.serialize().edges[index]);
assert(graph.stats.edges === 1);

// $FlowFixMe[cannot-write]
AdjacencyList.prototype.hash = originalHash;
});

it('addEdge should replace a deleted edge', () => {
let graph = new AdjacencyList({edgeCapacity: 2});
// Mock hash fn to generate collisions
// $FlowFixMe[method-unbinding]
let originalHash = AdjacencyList.prototype.hash;
// $FlowFixMe[cannot-write]
graph.hash = () => 1;
AdjacencyList.prototype.hash = () => 1;

let graph = new AdjacencyList();
let n0 = graph.addNode();
let n1 = graph.addNode();
graph.addEdge(n0, n1, 1);
graph.addEdge(n0, n1, 2);
// $FlowFixMe[incompatible-type]
let index: number = graph.indexOf(n0, n1, 2);
assert(graph.serialize().edges[index] > 0);
assert(graph.serialize().edges[index]);
graph.removeEdge(n0, n1, 2);
assert(!graph.serialize().edges[index]);
graph.addEdge(n0, n1, 2);
assert(graph.serialize().edges[index] > 0);
assert(graph.serialize().edges[index]);
// $FlowFixMe[incompatible-type]
let index2: number = graph.indexOf(n0, n1, 2);
assert(!graph.serialize().edges[index]);
assert(graph.serialize().edges[index2]);
// Resize to reclaim deleted edge space.
graph.resizeEdges(4);
// $FlowFixMe[incompatible-type]
let index3: number = graph.indexOf(n0, n1, 2);
assert(!graph.serialize().edges[index]);
assert(!graph.serialize().edges[index2]);
assert(graph.serialize().edges[index3]);

// $FlowFixMe[cannot-write]
AdjacencyList.prototype.hash = originalHash;
});

describe('deserialize', function() {
Expand Down

0 comments on commit e047e46

Please sign in to comment.