提交 ac41cd11 编写于 作者: D Dirk Baeumer

Implement LRU Cache on Linked Map

上级 1b775259
......@@ -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();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册