«Web development is not real development and is henceforth easier.»
«Web development is trivial and web developers don't need fancy data structures or any solid knowledge in algorithmics.»
Array
➡ lists of thingsObject
➡ key-value associationsMap
and Set
with ES6// How about changing this:
const counts = {};
for (const item in something) {
if (!(item in counts))
counts[item] = 0;
counts[item]++;
}
// Into this:
const counts = new MultiSet();
for (const item in something)
counts.add(item);
Sure, you can "implement" graphs using only Array
and Object
™.
But:
Examples taken from the graphology library:
const graph = new Graph();
// Finding specific neighbors
const neighbors = graph.outNeighbors(node);
// Iterating over a node's edges
graph.forEachEdge(node, (edge, attributes) => {
console.log(attributes.weight);
});
Benchmarking code accurately is not easy.
It does not mean we cannot be clever about it.
"Hashmap" lookups are costly.
// You made 2 lookups
Graph.prototype.getNodeAttribute = function(node, data) {
if (this._nodes.has(node))
throw Error(...);
const data = this._nodes.get(node);
return data[name];
};
// You made only one
Graph.prototype.getNodeAttribute = function(node, data) {
const data = this._nodes.get(node);
if (typeof data === 'undefined')
throw Error(...);
return data[name];
};
# Result, 100k items
--------------------
Two lookups: 31.275ms
One lookup: 15.762ms
The engine is clever. But not that clever. (It improves frequently, though...)
The «let's code badly, the engine will clean up my mess» approach will not work.
// BAD!
const test = x => /regex/.test(x);
// GOOD!
const REGEX = /regex/;
const test = x => REGEX.test(x);
// BAAAAAD!
function(array) {
array.forEach(subarray => {
// You just created one function per subarray!
subarray.forEach(x => console.log(x));
});
}
// Why do you do that?
// If you are this kind of person, can we meet?
// I really want to understand.
const array = [1, 'two', '3', /four/, {five: new Date()}];
Uint8Array
, Float32Array
etc.And have your very own "C in JavaScript"™.
A linked list (with pointers):
------------------------------
head -> (a) -> (b) -> (c) -> ø
// Using object references as pointers
function LinkedListNode(value) {
this.next = null;
this.value = value;
}
// Changing a pointer
node.next = otherNode;
A linked list (rolling our own pointers):
-----------------------------------------
head = 0
values = [a, b, c]
next = [1, 2, 0]
// Using byte arrays (capacity is fixed)
function LinkedList(capacity) {
this.head = 0;
this.next = new Uint16Array(capacity);
this.values = new Array(capacity);
}
// Changing a pointer;
this.next[nodeIndex] = otherNodeIndex;
A ~doubly~ linked list:
-----------------------
head = 0
tail = 2
next = [1, 2, 0]
prev = [0, 1, 2]
Same as (with pointers):
------------------------
head -> (a) <-> (b) <-> (c) <- tail
A map to pointers & values:
---------------------------
items = {a: 0, b: 1, c: 2}
values = [a, b, c]
name | set | get1 | update | get2 | evict |
---|---|---|---|---|---|
mnemonist-object | 15314 | 69444 | 35026 | 68966 | 7949 |
tiny-lru | 6530 | 46296 | 37244 | 42017 | 5961 |
lru-fast | 5979 | 36832 | 32626 | 40900 | 5929 |
mnemonist-map | 6272 | 15785 | 10923 | 16077 | 3738 |
lru | 3927 | 5454 | 5001 | 5366 | 2827 |
simple-lru-cache | 3393 | 3855 | 3701 | 3899 | 2496 |
hyperlru-object | 3515 | 3953 | 4044 | 4102 | 2495 |
js-lru | 3813 | 10010 | 9246 | 10309 | 1843 |
Everything is costly. Life is harsh.
This means that rolling your own stack will always beat recursion.
// Recursive version - "easy"
function recurse(node, key) {
if (key < node.value) {
if (node.left)
return recurse(node.left, key);
return false;
}
else if (key > node.value) {
if (node.right)
return recurse(node.right, key);
return false;
}
return true;
}
// Iterative version - more alien but faster, mileage may vary
function iterative(root, key) {
const stack = [root];
while (stack.length) {
const node = stack.pop();
if (key < node.value) {
if (node.left)
stack.push(node.left);
else
break;
}
else if (key > node.value) {
if (node.right)
stack.push(node.right);
else
break;
}
return true;
}
return false;
}
Lots of shiny options:
Communication between those and JavaScript has a cost that negates the benefit.
This is only viable if you have long running code or don't need the bridge between the layer and JavaScript.
The ByteArray
tips absolutely don't work in python.
It's even slower if you use numpy
arrays. (you need to go full native).
To be efficient your code must be statically interpretable.
If you do that:
Optimizing JavaScript = squinting a little and pretending really hard that:
For now, there is no way to beat JavaScript's objects and maps when doing key-value association.
Yet...
Examples were taken from the following libraries: