mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
Implement LRU Cache on Linked Map
This commit is contained in:
+85
-18
@@ -37,6 +37,7 @@ export function getOrSet<K, V>(map: Map<K, V>, key: K, value: V): V {
|
||||
}
|
||||
|
||||
export interface ISerializedBoundedLinkedMap<T> {
|
||||
version?: string;
|
||||
entries: { key: string; value: T }[];
|
||||
}
|
||||
|
||||
@@ -621,14 +622,12 @@ interface Item<K, V> {
|
||||
value: V;
|
||||
}
|
||||
|
||||
export namespace Touch {
|
||||
export const None: 0 = 0;
|
||||
export const First: 1 = 1;
|
||||
export const Last: 2 = 2;
|
||||
export enum Touch {
|
||||
None = 0,
|
||||
AsOld = 1,
|
||||
AsNew = 2
|
||||
}
|
||||
|
||||
export type Touch = 0 | 1 | 2;
|
||||
|
||||
export class LinkedMap<K, V> {
|
||||
|
||||
private _map: Map<K, Item<K, V>>;
|
||||
@@ -662,11 +661,14 @@ export class LinkedMap<K, V> {
|
||||
return this._map.has(key);
|
||||
}
|
||||
|
||||
public get(key: K): V | undefined {
|
||||
public get(key: K, touch: Touch = Touch.None): V | undefined {
|
||||
const item = this._map.get(key);
|
||||
if (!item) {
|
||||
return undefined;
|
||||
}
|
||||
if (touch !== Touch.None) {
|
||||
this.touch(item, touch);
|
||||
}
|
||||
return item.value;
|
||||
}
|
||||
|
||||
@@ -683,10 +685,10 @@ export class LinkedMap<K, V> {
|
||||
case Touch.None:
|
||||
this.addItemLast(item);
|
||||
break;
|
||||
case Touch.First:
|
||||
case Touch.AsOld:
|
||||
this.addItemFirst(item);
|
||||
break;
|
||||
case Touch.Last:
|
||||
case Touch.AsNew:
|
||||
this.addItemLast(item);
|
||||
break;
|
||||
default:
|
||||
@@ -811,6 +813,26 @@ export class LinkedMap<K, V> {
|
||||
}
|
||||
*/
|
||||
|
||||
protected trimOld(newSize: number) {
|
||||
if (newSize >= this.size) {
|
||||
return;
|
||||
}
|
||||
if (newSize === 0) {
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
let current = this._head;
|
||||
let currentSize = this.size;
|
||||
while (current && currentSize > newSize) {
|
||||
this._map.delete(current.key);
|
||||
current = current.next;
|
||||
currentSize--;
|
||||
}
|
||||
this._head = current;
|
||||
this._size = currentSize;
|
||||
current.previous = void 0;
|
||||
}
|
||||
|
||||
private addItemFirst(item: Item<K, V>): void {
|
||||
// First time Insert
|
||||
if (!this._head && !this._tail) {
|
||||
@@ -839,8 +861,8 @@ export class LinkedMap<K, V> {
|
||||
|
||||
private removeItem(item: Item<K, V>): void {
|
||||
if (item === this._head && item === this._tail) {
|
||||
this._head = undefined;
|
||||
this._tail = undefined;
|
||||
this._head = void 0;
|
||||
this._tail = void 0;
|
||||
}
|
||||
else if (item === this._head) {
|
||||
this._head = item.next;
|
||||
@@ -863,11 +885,11 @@ export class LinkedMap<K, V> {
|
||||
if (!this._head || !this._tail) {
|
||||
throw new Error('Invalid list');
|
||||
}
|
||||
if ((touch !== Touch.First && touch !== Touch.Last)) {
|
||||
if ((touch !== Touch.AsOld && touch !== Touch.AsNew)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (touch === Touch.First) {
|
||||
if (touch === Touch.AsOld) {
|
||||
if (item === this._head) {
|
||||
return;
|
||||
}
|
||||
@@ -879,7 +901,7 @@ export class LinkedMap<K, V> {
|
||||
if (item === this._tail) {
|
||||
// previous must be defined since item was not head but is tail
|
||||
// So there are more than on item in the map
|
||||
previous!.next = undefined;
|
||||
previous!.next = void 0;
|
||||
this._tail = previous;
|
||||
}
|
||||
else {
|
||||
@@ -889,11 +911,11 @@ export class LinkedMap<K, V> {
|
||||
}
|
||||
|
||||
// Insert the node at head
|
||||
item.previous = undefined;
|
||||
item.previous = void 0;
|
||||
item.next = this._head;
|
||||
this._head.previous = item;
|
||||
this._head = item;
|
||||
} else if (touch === Touch.Last) {
|
||||
} else if (touch === Touch.AsNew) {
|
||||
if (item === this._tail) {
|
||||
return;
|
||||
}
|
||||
@@ -905,17 +927,62 @@ export class LinkedMap<K, V> {
|
||||
if (item === this._head) {
|
||||
// next must be defined since item was not tail but is head
|
||||
// So there are more than on item in the map
|
||||
next!.previous = undefined;
|
||||
next!.previous = void 0;
|
||||
this._head = next;
|
||||
} else {
|
||||
// Both next and previous are not undefined since item was neither head nor tail.
|
||||
next!.previous = previous;
|
||||
previous!.next = next;
|
||||
}
|
||||
item.next = undefined;
|
||||
item.next = void 0;
|
||||
item.previous = this._tail;
|
||||
this._tail.next = item;
|
||||
this._tail = item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LRUCache<K, V> extends LinkedMap<K, V> {
|
||||
|
||||
private _limit: number;
|
||||
private _ratio: number;
|
||||
|
||||
constructor(limit: number, ratio: number = 1) {
|
||||
super();
|
||||
this._limit = limit;
|
||||
this._ratio = Math.min(Math.max(0, ratio), 1);
|
||||
}
|
||||
|
||||
public get limit(): number {
|
||||
return this._limit;
|
||||
}
|
||||
|
||||
public set limit(limit: number) {
|
||||
this._limit = limit;
|
||||
this.checkTrim();
|
||||
}
|
||||
|
||||
public get ratio(): number {
|
||||
return this._ratio;
|
||||
}
|
||||
|
||||
public set ratio(ratio: number) {
|
||||
this._ratio = Math.min(Math.max(0, ratio), 1);
|
||||
this.checkTrim();
|
||||
}
|
||||
|
||||
public get(key: K): V | undefined {
|
||||
return super.get(key, Touch.AsNew);
|
||||
}
|
||||
|
||||
public set(key: K, value: V): void {
|
||||
super.set(key, value, Touch.AsNew);
|
||||
this.checkTrim();
|
||||
}
|
||||
|
||||
private checkTrim() {
|
||||
if (this.size > this._limit) {
|
||||
this.trimOld(Math.round(this._limit * this._ratio));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,186 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
import { BoundedMap, ResourceMap, TernarySearchTree, PathIterator, StringIterator } from 'vs/base/common/map';
|
||||
import { BoundedMap, ResourceMap, TernarySearchTree, PathIterator, StringIterator, LinkedMap, Touch, LRUCache } from 'vs/base/common/map';
|
||||
import * as assert from 'assert';
|
||||
import URI from 'vs/base/common/uri';
|
||||
|
||||
suite('Map', () => {
|
||||
|
||||
test('LinkedMap - Simple', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('bk', 'bv');
|
||||
assert.deepStrictEqual(map.keys(), ['ak', 'bk']);
|
||||
assert.deepStrictEqual(map.values(), ['av', 'bv']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch Old one', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('ak', 'av', Touch.AsOld);
|
||||
assert.deepStrictEqual(map.keys(), ['ak']);
|
||||
assert.deepStrictEqual(map.values(), ['av']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch New one', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('ak', 'av', Touch.AsNew);
|
||||
assert.deepStrictEqual(map.keys(), ['ak']);
|
||||
assert.deepStrictEqual(map.values(), ['av']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch Old two', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('bk', 'bv');
|
||||
map.set('bk', 'bv', Touch.AsOld);
|
||||
assert.deepStrictEqual(map.keys(), ['bk', 'ak']);
|
||||
assert.deepStrictEqual(map.values(), ['bv', 'av']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch New two', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('bk', 'bv');
|
||||
map.set('ak', 'av', Touch.AsNew);
|
||||
assert.deepStrictEqual(map.keys(), ['bk', 'ak']);
|
||||
assert.deepStrictEqual(map.values(), ['bv', 'av']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch Old from middle', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('bk', 'bv');
|
||||
map.set('ck', 'cv');
|
||||
map.set('bk', 'bv', Touch.AsOld);
|
||||
assert.deepStrictEqual(map.keys(), ['bk', 'ak', 'ck']);
|
||||
assert.deepStrictEqual(map.values(), ['bv', 'av', 'cv']);
|
||||
});
|
||||
|
||||
test('LinkedMap - Touch New from middle', () => {
|
||||
let map = new LinkedMap<string, string>();
|
||||
map.set('ak', 'av');
|
||||
map.set('bk', 'bv');
|
||||
map.set('ck', 'cv');
|
||||
map.set('bk', 'bv', Touch.AsNew);
|
||||
assert.deepStrictEqual(map.keys(), ['ak', 'ck', 'bk']);
|
||||
assert.deepStrictEqual(map.values(), ['av', 'cv', 'bv']);
|
||||
});
|
||||
|
||||
test('LinkedMap - basics', function () {
|
||||
const map = new LinkedMap<string, any>();
|
||||
|
||||
assert.equal(map.size, 0);
|
||||
|
||||
map.set('1', 1);
|
||||
map.set('2', '2');
|
||||
map.set('3', true);
|
||||
|
||||
const obj = Object.create(null);
|
||||
map.set('4', obj);
|
||||
|
||||
const date = Date.now();
|
||||
map.set('5', date);
|
||||
|
||||
assert.equal(map.size, 5);
|
||||
assert.equal(map.get('1'), 1);
|
||||
assert.equal(map.get('2'), '2');
|
||||
assert.equal(map.get('3'), true);
|
||||
assert.equal(map.get('4'), obj);
|
||||
assert.equal(map.get('5'), date);
|
||||
assert.ok(!map.get('6'));
|
||||
|
||||
map.delete('6');
|
||||
assert.equal(map.size, 5);
|
||||
assert.equal(map.delete('1'), true);
|
||||
assert.equal(map.delete('2'), true);
|
||||
assert.equal(map.delete('3'), true);
|
||||
assert.equal(map.delete('4'), true);
|
||||
assert.equal(map.delete('5'), true);
|
||||
|
||||
assert.equal(map.size, 0);
|
||||
assert.ok(!map.get('5'));
|
||||
assert.ok(!map.get('4'));
|
||||
assert.ok(!map.get('3'));
|
||||
assert.ok(!map.get('2'));
|
||||
assert.ok(!map.get('1'));
|
||||
|
||||
map.set('1', 1);
|
||||
map.set('2', '2');
|
||||
map.set('3', true);
|
||||
|
||||
assert.ok(map.has('1'));
|
||||
assert.equal(map.get('1'), 1);
|
||||
assert.equal(map.get('2'), '2');
|
||||
assert.equal(map.get('3'), true);
|
||||
|
||||
map.clear();
|
||||
|
||||
assert.equal(map.size, 0);
|
||||
assert.ok(!map.get('1'));
|
||||
assert.ok(!map.get('2'));
|
||||
assert.ok(!map.get('3'));
|
||||
assert.ok(!map.has('1'));
|
||||
});
|
||||
|
||||
test('LinkedMap - LRUCache simple', () => {
|
||||
const cache = new LRUCache<number, number>(5);
|
||||
|
||||
[1, 2, 3, 4, 5].forEach(value => cache.set(value, value));
|
||||
assert.strictEqual(cache.size, 5);
|
||||
cache.set(6, 6);
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual(cache.keys(), [2, 3, 4, 5, 6]);
|
||||
cache.set(7, 7);
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual(cache.keys(), [3, 4, 5, 6, 7]);
|
||||
let values: number[] = [];
|
||||
[3, 4, 5, 6, 7].forEach(key => values.push(cache.get(key)));
|
||||
assert.deepStrictEqual(values, [3, 4, 5, 6, 7]);
|
||||
});
|
||||
|
||||
test('LinkedMap - LRU Cache limit', () => {
|
||||
const cache = new LRUCache<number, number>(10);
|
||||
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
cache.set(i, i);
|
||||
}
|
||||
assert.strictEqual(cache.size, 10);
|
||||
cache.limit = 5;
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual(cache.keys(), [6, 7, 8, 9, 10]);
|
||||
cache.limit = 20;
|
||||
assert.strictEqual(cache.size, 5);
|
||||
for (let i = 11; i <= 20; i++) {
|
||||
cache.set(i, i);
|
||||
}
|
||||
assert.deepEqual(cache.size, 15);
|
||||
let values: number[] = [];
|
||||
for (let i = 6; i <= 20; i++) {
|
||||
values.push(cache.get(i));
|
||||
assert.strictEqual(cache.get(i), i);
|
||||
}
|
||||
assert.deepStrictEqual(cache.values(), values);
|
||||
});
|
||||
|
||||
test('LinkedMap - LRU Cache limit with ration', () => {
|
||||
const cache = new LRUCache<number, number>(10, 0.5);
|
||||
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
cache.set(i, i);
|
||||
}
|
||||
assert.strictEqual(cache.size, 10);
|
||||
cache.set(11, 11);
|
||||
assert.strictEqual(cache.size, 5);
|
||||
assert.deepStrictEqual(cache.keys(), [7, 8, 9, 10, 11]);
|
||||
let values: number[] = [];
|
||||
cache.keys().forEach(key => values.push(cache.get(key)));
|
||||
assert.deepStrictEqual(values, [7, 8, 9, 10, 11]);
|
||||
assert.deepStrictEqual(cache.values(), values);
|
||||
});
|
||||
|
||||
test('BoundedMap - basics', function () {
|
||||
const map = new BoundedMap<any>();
|
||||
|
||||
|
||||
@@ -1188,7 +1188,7 @@ class TaskService implements ITaskService {
|
||||
let executeResult = this.getTaskSystem().run(task, resolver);
|
||||
let key = Task.getRecentlyUsedKey(task);
|
||||
if (key) {
|
||||
this.getRecentlyUsedTasks().set(key, key, Touch.First);
|
||||
this.getRecentlyUsedTasks().set(key, key, Touch.AsOld);
|
||||
}
|
||||
if (executeResult.kind === TaskExecuteKind.Active) {
|
||||
let active = executeResult.active;
|
||||
|
||||
@@ -280,7 +280,7 @@ export class TerminalTaskSystem implements ITaskSystem {
|
||||
this.sameTaskTerminals[key] = terminal.id.toString();
|
||||
break;
|
||||
case PanelKind.Shared:
|
||||
this.idleTaskTerminals.set(key, terminal.id.toString(), Touch.First);
|
||||
this.idleTaskTerminals.set(key, terminal.id.toString(), Touch.AsOld);
|
||||
break;
|
||||
}
|
||||
watchingProblemMatcher.done();
|
||||
@@ -322,7 +322,7 @@ export class TerminalTaskSystem implements ITaskSystem {
|
||||
this.sameTaskTerminals[key] = terminal.id.toString();
|
||||
break;
|
||||
case PanelKind.Shared:
|
||||
this.idleTaskTerminals.set(key, terminal.id.toString(), Touch.First);
|
||||
this.idleTaskTerminals.set(key, terminal.id.toString(), Touch.AsOld);
|
||||
break;
|
||||
}
|
||||
startStopProblemMatcher.done();
|
||||
|
||||
Reference in New Issue
Block a user