Skip to content

Commit 1125869

Browse files
authored
Merge pull request #172 from ChainSafe/tuyen/time-cache
Implement SimpleTimeCache
2 parents 1843887 + 2ac5eca commit 1125869

File tree

5 files changed

+123
-7
lines changed

5 files changed

+123
-7
lines changed

package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"build:types": "aegir build --no-types",
2020
"prepare": "npm run build",
2121
"pretest": "npm run build",
22+
"benchmark": "node ./node_modules/.bin/benchmark 'test/benchmark/time-cache.test.js' --local",
2223
"test": "aegir test",
2324
"test:node": "aegir test --target node",
2425
"test:browser": "aegir test --target browser"
@@ -47,7 +48,6 @@
4748
"libp2p-interfaces": "^2.0.1",
4849
"peer-id": "^0.16.0",
4950
"protobufjs": "^6.11.2",
50-
"time-cache": "^0.3.0",
5151
"uint8arrays": "^3.0.0"
5252
},
5353
"devDependencies": {
@@ -83,6 +83,8 @@
8383
"p-wait-for": "^3.1.0",
8484
"promisify-es6": "^1.0.3",
8585
"sinon": "^11.1.1",
86+
"time-cache": "^0.3.0",
87+
"@dapplion/benchmark": "^0.1.6",
8688
"typescript": "4.0.x",
8789
"util": "^0.12.3"
8890
},

test/benchmark/time-cache.test.js

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const {itBench, setBenchOpts} = require("@dapplion/benchmark");
2+
const TimeCache = require('time-cache')
3+
const {SimpleTimeCache} = require("../../src/utils/time-cache");
4+
5+
describe("npm TimeCache vs SimpleTimeCache", () => {
6+
setBenchOpts({
7+
maxMs: 100 * 1000,
8+
minMs: 60 * 1000,
9+
runs: 512,
10+
});
11+
12+
const iterations = [1_000_000, 4_000_000, 8_000_000, 16_000_000];
13+
const timeCache = new TimeCache({ validity: 1 });
14+
const simpleTimeCache = new SimpleTimeCache({ validityMs: 1000 });
15+
16+
for (const iteration of iterations) {
17+
itBench(`npm TimeCache.put x${iteration}`, () => {
18+
for (let j = 0; j < iteration; j++) timeCache.put(String(j));
19+
});
20+
21+
itBench(`SimpleTimeCache.put x${iteration}`, () => {
22+
for (let j = 0; j < iteration; j++) simpleTimeCache.put(String(j));
23+
});
24+
}
25+
26+
});

test/time-cache.spec.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const {expect} = require("chai")
2+
const {SimpleTimeCache} = require("../src/utils/time-cache")
3+
const sinon = require("sinon")
4+
5+
describe("SimpleTimeCache", () => {
6+
const validityMs = 1000
7+
const timeCache = new SimpleTimeCache({validityMs})
8+
const sandbox = sinon.createSandbox()
9+
10+
beforeEach(() => {
11+
sandbox.useFakeTimers()
12+
})
13+
14+
afterEach(() => {
15+
sandbox.restore()
16+
})
17+
18+
19+
it("should delete items after 1sec", () => {
20+
timeCache.put("aFirst")
21+
timeCache.put("bFirst")
22+
timeCache.put("cFirst")
23+
24+
expect(timeCache.has("aFirst")).to.be.true
25+
expect(timeCache.has("bFirst")).to.be.true
26+
expect(timeCache.has("cFirst")).to.be.true
27+
28+
sandbox.clock.tick(validityMs)
29+
30+
timeCache.put("aSecond")
31+
timeCache.put("bSecond")
32+
timeCache.put("cSecond")
33+
34+
expect(timeCache.has("aSecond")).to.be.true
35+
expect(timeCache.has("bSecond")).to.be.true
36+
expect(timeCache.has("cSecond")).to.be.true
37+
expect(timeCache.has("aFirst")).to.be.false
38+
expect(timeCache.has("bFirst")).to.be.false
39+
expect(timeCache.has("cFirst")).to.be.false
40+
})
41+
})

ts/index.ts

+4-6
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,11 @@ import { createGossipRpc, shuffle, hasGossipProtocol, messageIdToString } from '
88
import { PeerScore, PeerScoreParams, PeerScoreThresholds, createPeerScoreParams, createPeerScoreThresholds } from './score'
99
import { IWantTracer } from './tracer'
1010
import { AddrInfo, MessageIdFunction } from './interfaces'
11+
import { SimpleTimeCache } from './utils/time-cache'
1112
import { Debugger } from 'debug'
1213
import Libp2p from 'libp2p'
1314

1415
import PeerStreams from 'libp2p-interfaces/src/pubsub/peer-streams'
15-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
16-
// @ts-ignore
17-
import TimeCache = require('time-cache')
1816
import PeerId = require('peer-id')
1917
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
2018
// @ts-ignore
@@ -89,7 +87,7 @@ interface GossipOptions extends GossipInputOptions {
8987
class Gossipsub extends Pubsub {
9088
peers: Map<string, PeerStreams>
9189
direct: Set<string>
92-
seenCache: TimeCache
90+
seenCache: SimpleTimeCache
9391
topics: Map<string, Set<string>>
9492
mesh: Map<string, Set<string>>
9593
fanout: Map<string, Set<string>>
@@ -192,9 +190,9 @@ class Gossipsub extends Pubsub {
192190
/**
193191
* Cache of seen messages
194192
*
195-
* @type {TimeCache}
193+
* @type {SimpleTimeCache}
196194
*/
197-
this.seenCache = new TimeCache({ validity: opts.seenTTL / 1000 })
195+
this.seenCache = new SimpleTimeCache({ validityMs: opts.seenTTL })
198196

199197
/**
200198
* Map of topic meshes

ts/utils/time-cache.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
type SimpleTimeCacheOpts = {
2+
validityMs: number
3+
}
4+
5+
/**
6+
* This is similar to https://github.com/daviddias/time-cache/blob/master/src/index.js
7+
* for our own need, we don't use lodash throttle to improve performance.
8+
* This gives 4x - 5x performance gain compared to npm TimeCache
9+
*/
10+
export class SimpleTimeCache {
11+
private entries: Map<string, number>;
12+
private validityMs: number;
13+
private lastPruneTime = 0;
14+
15+
constructor (options: SimpleTimeCacheOpts) {
16+
this.entries = new Map()
17+
this.validityMs = options.validityMs
18+
19+
if (this.validityMs <= 0) {
20+
throw Error(`Invalid validityMs ${this.validityMs}`)
21+
}
22+
}
23+
24+
put (key: string): void {
25+
this.entries.set(key, Date.now())
26+
this.prune()
27+
}
28+
29+
prune (): void {
30+
const now = Date.now()
31+
if (now - this.lastPruneTime < 200) {
32+
return
33+
}
34+
this.lastPruneTime = now
35+
36+
for (const [k, v] of this.entries.entries()) {
37+
if (v + this.validityMs < now) {
38+
this.entries.delete(k)
39+
} else {
40+
// sort by insertion order
41+
break
42+
}
43+
}
44+
}
45+
46+
has (key: string): boolean {
47+
return this.entries.has(key)
48+
}
49+
}

0 commit comments

Comments
 (0)