-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a new LRUMap class, remove uses of Map
LRUMap implements the parts of an ordered map that we need to efficiently implement DynamicCharAtlas. It's more code, but there's a few advantages of this approach: - Map isn't available on some older browsers, so this removes the need for a polyfill. - Moving an item to the end of the map's iteration order now only requires unlinking and linking a linked-list node, whereas before we had to delete and re-insert our value. - Peeking at the oldest entry in the map no longer requires allocating and destroying an iterator. - We can preallocate the linked-list nodes we want to improve cache locality. Similarly, we can recycle linked-list nodes to reduce allocations and the GC pauses those allocations may cause. - LRUMap seems to give slightly better results in Chrome's profiler than Map did. We now spend about 5% of our time on map operations instead of about 10%. - In my (limited) testing, it doesn't look like LRUMap is slowing down over time. Map appeared to get slightly slower the longer I ran the terminal for, either due to memory fragmentation or some sort of leak. I still need to write some tests for LRUMap, but I've been using this implementation for the last hour without problems.
- Loading branch information
Showing
3 changed files
with
131 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
/** | ||
* Copyright (c) 2017 The xterm.js authors. All rights reserved. | ||
* @license MIT | ||
*/ | ||
|
||
interface ILinkedListNode<T> { | ||
prev: ILinkedListNode<T>, | ||
next: ILinkedListNode<T>, | ||
key: string, | ||
value: T, | ||
} | ||
|
||
export default class LRUMap<T> { | ||
private _map = {}; | ||
private _head: ILinkedListNode<T> = null; | ||
private _tail: ILinkedListNode<T> = null; | ||
private _nodePool: ILinkedListNode<T>[] = []; | ||
public size: number = 0; | ||
|
||
constructor(public capacity: number) { } | ||
|
||
private _unlinkNode(node: ILinkedListNode<T>): void { | ||
const prev = node.prev; | ||
const next = node.next; | ||
if (node === this._head) { | ||
this._head = next; | ||
} | ||
if (node === this._tail) { | ||
this._tail = prev; | ||
} | ||
if (prev !== null) { | ||
prev.next = next; | ||
} | ||
if (next !== null) { | ||
next.prev = prev; | ||
} | ||
} | ||
|
||
private _appendNode(node: ILinkedListNode<T>): void { | ||
node.prev = this._tail; | ||
node.next = null; | ||
this._tail = node; | ||
if (this._head === null) { | ||
this._head = node; | ||
} | ||
} | ||
|
||
/** | ||
* Preallocate a bunch of linked-list nodes. Allocating these nodes ahead of time means that | ||
* they're more likely to live next to each other in memory, which seems to improve performance. | ||
* | ||
* Each empty object only consumes about 60 bytes of memory, so this is pretty cheap, even for | ||
* large maps. | ||
*/ | ||
public prealloc(count: number) { | ||
const nodePool = this._nodePool; | ||
for (let i = 0; i < count; i++) { | ||
nodePool.push({ | ||
prev: null, | ||
next: null, | ||
key: null, | ||
value: null, | ||
}); | ||
} | ||
} | ||
|
||
public get(key: string): T | null { | ||
// This is unsafe: We're assuming our keyspace doesn't overlap with Object.prototype. However, | ||
// it's faster than calling hasOwnProperty, and in our case, it would never overlap. | ||
const node = this._map[key]; | ||
if (node !== undefined) { | ||
this._unlinkNode(node); | ||
this._appendNode(node); | ||
return node.value; | ||
} | ||
return null; | ||
} | ||
|
||
public peek(): T | null { | ||
const head = this._head; | ||
return head === null ? null : head.value; | ||
} | ||
|
||
public set(key: string, value: T): void { | ||
// This is unsafe: See note above. | ||
let node = this._map[key]; | ||
if (node !== undefined) { | ||
// already exists, we just need to mutate it and move it to the end of the list | ||
node = this._map[key]; | ||
this._unlinkNode(node); | ||
node.value = |