chore(perf): cache partial fragments with LRU
parent
f5185d52d0
commit
bdaab0f8fd
|
@ -13,11 +13,14 @@
|
|||
const cheerio = require('cheerio');
|
||||
const { existsSync } = require('fs');
|
||||
const { relative, dirname, join, extname } = require('path');
|
||||
const { LRUMap } = require('../utils/lru');
|
||||
|
||||
const __archives = [];
|
||||
const __categories = [];
|
||||
const __tags = [];
|
||||
|
||||
const __fragmentCache = new LRUMap(20);
|
||||
|
||||
module.exports = function (hexo) {
|
||||
hexo.extend.helper.register('_list_archives', function () {
|
||||
if (__archives.length) {
|
||||
|
@ -183,6 +186,9 @@ module.exports = function (hexo) {
|
|||
const fragment = relative(view_dir, path.substring(0, path.length - '.locals.js'.length));
|
||||
const cacheId = [fragment, language, md5(JSON.stringify(_locals))].join('-');
|
||||
|
||||
return partial(name, _locals, { cache: cacheId, only: options.only || false });
|
||||
if (!__fragmentCache.has(cacheId)) {
|
||||
__fragmentCache.set(cacheId, partial(name, _locals, { cache: false, only: options.only || false }));
|
||||
}
|
||||
return __fragmentCache.get(cacheId);
|
||||
});
|
||||
}
|
|
@ -0,0 +1,305 @@
|
|||
/**
|
||||
* A doubly linked list-based Least Recently Used (LRU) cache. Will keep most
|
||||
* recently used items while discarding least recently used items when its limit
|
||||
* is reached.
|
||||
*
|
||||
* Licensed under MIT. Copyright (c) 2010 Rasmus Andersson <http://hunch.se/>
|
||||
* See README.md for details.
|
||||
*
|
||||
* Illustration of the design:
|
||||
*
|
||||
* entry entry entry entry
|
||||
* ______ ______ ______ ______
|
||||
* | head |.newer => | |.newer => | |.newer => | tail |
|
||||
* | A | | B | | C | | D |
|
||||
* |______| <= older.|______| <= older.|______| <= older.|______|
|
||||
*
|
||||
* removed <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- <-- added
|
||||
*/
|
||||
(function (g, f) {
|
||||
const e = typeof exports == 'object' ? exports : typeof g == 'object' ? g : {};
|
||||
f(e);
|
||||
if (typeof define == 'function' && define.amd) { define('lru', e); }
|
||||
})(this, function (exports) {
|
||||
|
||||
const NEWER = Symbol('newer');
|
||||
const OLDER = Symbol('older');
|
||||
|
||||
function LRUMap(limit, entries) {
|
||||
if (typeof limit !== 'number') {
|
||||
// called as (entries)
|
||||
entries = limit;
|
||||
limit = 0;
|
||||
}
|
||||
|
||||
this.size = 0;
|
||||
this.limit = limit;
|
||||
this.oldest = this.newest = undefined;
|
||||
this._keymap = new Map();
|
||||
|
||||
if (entries) {
|
||||
this.assign(entries);
|
||||
if (limit < 1) {
|
||||
this.limit = this.size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.LRUMap = LRUMap;
|
||||
|
||||
function Entry(key, value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this[NEWER] = undefined;
|
||||
this[OLDER] = undefined;
|
||||
}
|
||||
|
||||
|
||||
LRUMap.prototype._markEntryAsUsed = function (entry) {
|
||||
if (entry === this.newest) {
|
||||
// Already the most recenlty used entry, so no need to update the list
|
||||
return;
|
||||
}
|
||||
// HEAD--------------TAIL
|
||||
// <.older .newer>
|
||||
// <--- add direction --
|
||||
// A B C <D> E
|
||||
if (entry[NEWER]) {
|
||||
if (entry === this.oldest) {
|
||||
this.oldest = entry[NEWER];
|
||||
}
|
||||
entry[NEWER][OLDER] = entry[OLDER]; // C <-- E.
|
||||
}
|
||||
if (entry[OLDER]) {
|
||||
entry[OLDER][NEWER] = entry[NEWER]; // C. --> E
|
||||
}
|
||||
entry[NEWER] = undefined; // D --x
|
||||
entry[OLDER] = this.newest; // D. --> E
|
||||
if (this.newest) {
|
||||
this.newest[NEWER] = entry; // E. <-- D
|
||||
}
|
||||
this.newest = entry;
|
||||
};
|
||||
|
||||
LRUMap.prototype.assign = function (entries) {
|
||||
let entry, limit = this.limit || Number.MAX_VALUE;
|
||||
this._keymap.clear();
|
||||
let it = entries[Symbol.iterator]();
|
||||
for (let itv = it.next(); !itv.done; itv = it.next()) {
|
||||
let e = new Entry(itv.value[0], itv.value[1]);
|
||||
this._keymap.set(e.key, e);
|
||||
if (!entry) {
|
||||
this.oldest = e;
|
||||
} else {
|
||||
entry[NEWER] = e;
|
||||
e[OLDER] = entry;
|
||||
}
|
||||
entry = e;
|
||||
if (limit-- == 0) {
|
||||
throw new Error('overflow');
|
||||
}
|
||||
}
|
||||
this.newest = entry;
|
||||
this.size = this._keymap.size;
|
||||
};
|
||||
|
||||
LRUMap.prototype.get = function (key) {
|
||||
// First, find our cache entry
|
||||
var entry = this._keymap.get(key);
|
||||
if (!entry) return; // Not cached. Sorry.
|
||||
// As <key> was found in the cache, register it as being requested recently
|
||||
this._markEntryAsUsed(entry);
|
||||
return entry.value;
|
||||
};
|
||||
|
||||
LRUMap.prototype.set = function (key, value) {
|
||||
var entry = this._keymap.get(key);
|
||||
|
||||
if (entry) {
|
||||
// update existing
|
||||
entry.value = value;
|
||||
this._markEntryAsUsed(entry);
|
||||
return this;
|
||||
}
|
||||
|
||||
// new entry
|
||||
this._keymap.set(key, (entry = new Entry(key, value)));
|
||||
|
||||
if (this.newest) {
|
||||
// link previous tail to the new tail (entry)
|
||||
this.newest[NEWER] = entry;
|
||||
entry[OLDER] = this.newest;
|
||||
} else {
|
||||
// we're first in -- yay
|
||||
this.oldest = entry;
|
||||
}
|
||||
|
||||
// add new entry to the end of the linked list -- it's now the freshest entry.
|
||||
this.newest = entry;
|
||||
++this.size;
|
||||
if (this.size > this.limit) {
|
||||
// we hit the limit -- remove the head
|
||||
this.shift();
|
||||
}
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
LRUMap.prototype.shift = function () {
|
||||
// todo: handle special case when limit == 1
|
||||
var entry = this.oldest;
|
||||
if (entry) {
|
||||
if (this.oldest[NEWER]) {
|
||||
// advance the list
|
||||
this.oldest = this.oldest[NEWER];
|
||||
this.oldest[OLDER] = undefined;
|
||||
} else {
|
||||
// the cache is exhausted
|
||||
this.oldest = undefined;
|
||||
this.newest = undefined;
|
||||
}
|
||||
// Remove last strong reference to <entry> and remove links from the purged
|
||||
// entry being returned:
|
||||
entry[NEWER] = entry[OLDER] = undefined;
|
||||
this._keymap.delete(entry.key);
|
||||
--this.size;
|
||||
return [entry.key, entry.value];
|
||||
}
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Following code is optional and can be removed without breaking the core
|
||||
// functionality.
|
||||
|
||||
LRUMap.prototype.find = function (key) {
|
||||
let e = this._keymap.get(key);
|
||||
return e ? e.value : undefined;
|
||||
};
|
||||
|
||||
LRUMap.prototype.has = function (key) {
|
||||
return this._keymap.has(key);
|
||||
};
|
||||
|
||||
LRUMap.prototype['delete'] = function (key) {
|
||||
var entry = this._keymap.get(key);
|
||||
if (!entry) return;
|
||||
this._keymap.delete(entry.key);
|
||||
if (entry[NEWER] && entry[OLDER]) {
|
||||
// relink the older entry with the newer entry
|
||||
entry[OLDER][NEWER] = entry[NEWER];
|
||||
entry[NEWER][OLDER] = entry[OLDER];
|
||||
} else if (entry[NEWER]) {
|
||||
// remove the link to us
|
||||
entry[NEWER][OLDER] = undefined;
|
||||
// link the newer entry to head
|
||||
this.oldest = entry[NEWER];
|
||||
} else if (entry[OLDER]) {
|
||||
// remove the link to us
|
||||
entry[OLDER][NEWER] = undefined;
|
||||
// link the newer entry to head
|
||||
this.newest = entry[OLDER];
|
||||
} else {// if(entry[OLDER] === undefined && entry.newer === undefined) {
|
||||
this.oldest = this.newest = undefined;
|
||||
}
|
||||
|
||||
this.size--;
|
||||
return entry.value;
|
||||
};
|
||||
|
||||
LRUMap.prototype.clear = function () {
|
||||
// Not clearing links should be safe, as we don't expose live links to user
|
||||
this.oldest = this.newest = undefined;
|
||||
this.size = 0;
|
||||
this._keymap.clear();
|
||||
};
|
||||
|
||||
|
||||
function EntryIterator(oldestEntry) { this.entry = oldestEntry; }
|
||||
EntryIterator.prototype[Symbol.iterator] = function () { return this; }
|
||||
EntryIterator.prototype.next = function () {
|
||||
let ent = this.entry;
|
||||
if (ent) {
|
||||
this.entry = ent[NEWER];
|
||||
return { done: false, value: [ent.key, ent.value] };
|
||||
} else {
|
||||
return { done: true, value: undefined };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function KeyIterator(oldestEntry) { this.entry = oldestEntry; }
|
||||
KeyIterator.prototype[Symbol.iterator] = function () { return this; }
|
||||
KeyIterator.prototype.next = function () {
|
||||
let ent = this.entry;
|
||||
if (ent) {
|
||||
this.entry = ent[NEWER];
|
||||
return { done: false, value: ent.key };
|
||||
} else {
|
||||
return { done: true, value: undefined };
|
||||
}
|
||||
};
|
||||
|
||||
function ValueIterator(oldestEntry) { this.entry = oldestEntry; }
|
||||
ValueIterator.prototype[Symbol.iterator] = function () { return this; }
|
||||
ValueIterator.prototype.next = function () {
|
||||
let ent = this.entry;
|
||||
if (ent) {
|
||||
this.entry = ent[NEWER];
|
||||
return { done: false, value: ent.value };
|
||||
} else {
|
||||
return { done: true, value: undefined };
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
LRUMap.prototype.keys = function () {
|
||||
return new KeyIterator(this.oldest);
|
||||
};
|
||||
|
||||
LRUMap.prototype.values = function () {
|
||||
return new ValueIterator(this.oldest);
|
||||
};
|
||||
|
||||
LRUMap.prototype.entries = function () {
|
||||
return this;
|
||||
};
|
||||
|
||||
LRUMap.prototype[Symbol.iterator] = function () {
|
||||
return new EntryIterator(this.oldest);
|
||||
};
|
||||
|
||||
LRUMap.prototype.forEach = function (fun, thisObj) {
|
||||
if (typeof thisObj !== 'object') {
|
||||
thisObj = this;
|
||||
}
|
||||
let entry = this.oldest;
|
||||
while (entry) {
|
||||
fun.call(thisObj, entry.value, entry.key, this);
|
||||
entry = entry[NEWER];
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns a JSON (array) representation */
|
||||
LRUMap.prototype.toJSON = function () {
|
||||
var s = new Array(this.size), i = 0, entry = this.oldest;
|
||||
while (entry) {
|
||||
s[i++] = { key: entry.key, value: entry.value };
|
||||
entry = entry[NEWER];
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
/** Returns a String representation */
|
||||
LRUMap.prototype.toString = function () {
|
||||
var s = '', entry = this.oldest;
|
||||
while (entry) {
|
||||
s += String(entry.key) + ':' + entry.value;
|
||||
entry = entry[NEWER];
|
||||
if (entry) {
|
||||
s += ' < ';
|
||||
}
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
});
|
Loading…
Reference in New Issue