mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2026-08-25 04:08:54 +00:00
More optimisations
These ones stemming from a wiki with 80K tiddlers and 5K tags, and a total wiki size of 150MB. > The biggest win is the P50 (median) refresh dropping from 124ms to 46ms — a 63% improvement
This commit is contained in:
@@ -1154,6 +1154,30 @@ $tw.Wiki = function(options) {
|
||||
pluginTiddlers = [], // Array of tiddlers containing registered plugins, ordered by priority
|
||||
pluginInfo = Object.create(null), // Hashmap of parsed plugin content
|
||||
shadowTiddlers = Object.create(null), // Hashmap by title of {source:, tiddler:}
|
||||
systemTiddlerTitles = null, // Array of system tiddler titles (starting with "$:/")
|
||||
nonSystemTiddlerTitles = null, // Array of non-system tiddler titles
|
||||
partitionTiddlerTitles = function() {
|
||||
if(systemTiddlerTitles === null) {
|
||||
systemTiddlerTitles = [];
|
||||
nonSystemTiddlerTitles = [];
|
||||
var titles = getTiddlerTitles();
|
||||
for(var i = 0, length = titles.length; i < length; i++) {
|
||||
if(titles[i].indexOf("$:/") === 0) {
|
||||
systemTiddlerTitles.push(titles[i]);
|
||||
} else {
|
||||
nonSystemTiddlerTitles.push(titles[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
getSystemTiddlerTitles = function() {
|
||||
partitionTiddlerTitles();
|
||||
return systemTiddlerTitles;
|
||||
},
|
||||
getNonSystemTiddlerTitles = function() {
|
||||
partitionTiddlerTitles();
|
||||
return nonSystemTiddlerTitles;
|
||||
},
|
||||
shadowTiddlerTitles = null,
|
||||
getShadowTiddlerTitles = function() {
|
||||
if(!shadowTiddlerTitles) {
|
||||
@@ -1202,6 +1226,14 @@ $tw.Wiki = function(options) {
|
||||
tiddlers[title] = tiddler;
|
||||
// Check we've got the title
|
||||
tiddlerTitles = $tw.utils.insertSortedArray(tiddlerTitles || [],title);
|
||||
// Maintain system/non-system partitions
|
||||
if(systemTiddlerTitles !== null) {
|
||||
if(title.indexOf("$:/") === 0) {
|
||||
$tw.utils.insertSortedArray(systemTiddlerTitles,title);
|
||||
} else {
|
||||
$tw.utils.insertSortedArray(nonSystemTiddlerTitles,title);
|
||||
}
|
||||
}
|
||||
// Record the new tiddler state
|
||||
updateDescriptor["new"] = {
|
||||
tiddler: tiddler,
|
||||
@@ -1242,6 +1274,14 @@ $tw.Wiki = function(options) {
|
||||
tiddlerTitles.splice(index,1);
|
||||
}
|
||||
}
|
||||
// Delete from system/non-system partitions
|
||||
if(systemTiddlerTitles !== null) {
|
||||
var partitionArray = title.indexOf("$:/") === 0 ? systemTiddlerTitles : nonSystemTiddlerTitles;
|
||||
var partitionIndex = partitionArray.indexOf(title);
|
||||
if(partitionIndex !== -1) {
|
||||
partitionArray.splice(partitionIndex,1);
|
||||
}
|
||||
}
|
||||
// Record the new tiddler state
|
||||
updateDescriptor["new"] = {
|
||||
tiddler: this.getTiddler(title),
|
||||
@@ -1280,6 +1320,16 @@ $tw.Wiki = function(options) {
|
||||
return getTiddlerTitles().slice(0);
|
||||
};
|
||||
|
||||
// Get an array of all system tiddler titles (returns cached array; do not mutate)
|
||||
this.allSystemTitles = function() {
|
||||
return getSystemTiddlerTitles();
|
||||
};
|
||||
|
||||
// Get an array of all non-system tiddler titles (returns cached array; do not mutate)
|
||||
this.allNonSystemTitles = function() {
|
||||
return getNonSystemTiddlerTitles();
|
||||
};
|
||||
|
||||
// Iterate through all tiddler titles
|
||||
this.each = function(callback) {
|
||||
var titles = getTiddlerTitles(),
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ $tw.boot = $tw.boot || Object.create(null);
|
||||
|
||||
// Config
|
||||
$tw.config = $tw.config || Object.create(null);
|
||||
$tw.config.maxEditFileSize = 100 * 1024 * 1024; // 100MB
|
||||
$tw.config.maxEditFileSize = 200 * 1024 * 1024; // 200MB
|
||||
|
||||
// Detect platforms
|
||||
if(!("browser" in $tw)) {
|
||||
|
||||
+34
-17
@@ -261,6 +261,7 @@ exports.compileFilter = function(filterString) {
|
||||
var filterOperators = this.getFilterOperators();
|
||||
// Assemble array of functions, one for each operation
|
||||
var operationFunctions = [];
|
||||
var operationSubFunctions = []; // Unwrapped sub-functions for fast path
|
||||
// Step through the operations
|
||||
var self = this;
|
||||
$tw.utils.each(filterParseTree,function(operation) {
|
||||
@@ -289,20 +290,24 @@ exports.compileFilter = function(filterString) {
|
||||
operand.value = self.getTextReference(operand.text,"",currTiddlerTitle);
|
||||
operand.multiValue = [operand.value];
|
||||
} else if(operand.variable) {
|
||||
var varTree = $tw.utils.parseFilterVariable(operand.text);
|
||||
operand.value = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source})[0] || "";
|
||||
if(!operand._varTree) {
|
||||
operand._varTree = $tw.utils.parseFilterVariable(operand.text);
|
||||
}
|
||||
operand.value = widgetClass.evaluateVariable(widget,operand._varTree.name,{params: operand._varTree.params, source: source})[0] || "";
|
||||
operand.multiValue = [operand.value];
|
||||
} else if(operand.multiValuedVariable) {
|
||||
var varTree = $tw.utils.parseFilterVariable(operand.text);
|
||||
var resultList = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source});
|
||||
if(!operand._varTree) {
|
||||
operand._varTree = $tw.utils.parseFilterVariable(operand.text);
|
||||
}
|
||||
var resultList = widgetClass.evaluateVariable(widget,operand._varTree.name,{params: operand._varTree.params, source: source});
|
||||
if((resultList.length > 0 && resultList[0] !== undefined) || resultList.length === 0) {
|
||||
operand.multiValue = widgetClass.evaluateVariable(widget,varTree.name,{params: varTree.params, source: source}) || [];
|
||||
operand.multiValue = widgetClass.evaluateVariable(widget,operand._varTree.name,{params: operand._varTree.params, source: source}) || [];
|
||||
operand.value = operand.multiValue[0] || "";
|
||||
} else {
|
||||
operand.value = "";
|
||||
operand.multiValue = [];
|
||||
}
|
||||
operand.isMultiValueOperand = true;
|
||||
operand.isMultiValueOperand = true;
|
||||
} else {
|
||||
operand.value = operand.text;
|
||||
operand.multiValue = [operand.value];
|
||||
@@ -343,6 +348,7 @@ exports.compileFilter = function(filterString) {
|
||||
return resultArray;
|
||||
}
|
||||
};
|
||||
operationSubFunctions.push(operationSubFunction);
|
||||
var filterRunPrefixes = self.getFilterRunPrefixes();
|
||||
// Wrap the operator functions in a wrapper function that depends on the prefix
|
||||
operationFunctions.push((function() {
|
||||
@@ -372,6 +378,10 @@ exports.compileFilter = function(filterString) {
|
||||
}
|
||||
})());
|
||||
});
|
||||
// Detect single "or" run for fast path (bypass LinkedList)
|
||||
var isSingleOrRun = filterParseTree.length === 1 &&
|
||||
(!filterParseTree[0].prefix || filterParseTree[0].prefix === "");
|
||||
var singleOrSubFunction = isSingleOrRun ? operationSubFunctions[0] : null;
|
||||
// Return a function that applies the operations to a source iterator of tiddler titles
|
||||
var fnMeasured = $tw.perf.measure("filter: " + filterString,function filterFunction(source,widget) {
|
||||
if(!source) {
|
||||
@@ -382,23 +392,30 @@ exports.compileFilter = function(filterString) {
|
||||
if(!widget) {
|
||||
widget = $tw.rootWidget;
|
||||
}
|
||||
var results = new $tw.utils.LinkedList();
|
||||
self.filterRecursionCount = (self.filterRecursionCount || 0) + 1;
|
||||
var resultArray;
|
||||
if(self.filterRecursionCount < MAX_FILTER_DEPTH) {
|
||||
$tw.utils.each(operationFunctions,function(operationFunction) {
|
||||
var operationResult = operationFunction(results,source,widget);
|
||||
if(operationResult) {
|
||||
if(operationResult.variables) {
|
||||
// If the filter run prefix has returned variables, create a new fake widget with those variables
|
||||
widget = widget.makeFakeWidgetWithVariables(operationResult.variables);
|
||||
if(singleOrSubFunction) {
|
||||
// Fast path: single "or" run, return array directly without LinkedList
|
||||
resultArray = singleOrSubFunction(source,widget);
|
||||
} else {
|
||||
var results = new $tw.utils.LinkedList();
|
||||
$tw.utils.each(operationFunctions,function(operationFunction) {
|
||||
var operationResult = operationFunction(results,source,widget);
|
||||
if(operationResult) {
|
||||
if(operationResult.variables) {
|
||||
// If the filter run prefix has returned variables, create a new fake widget with those variables
|
||||
widget = widget.makeFakeWidgetWithVariables(operationResult.variables);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
resultArray = results.toArray();
|
||||
}
|
||||
} else {
|
||||
results.push("/**-- Excessive filter recursion --**/");
|
||||
resultArray = ["/**-- Excessive filter recursion --**/"];
|
||||
}
|
||||
self.filterRecursionCount = self.filterRecursionCount - 1;
|
||||
return results.toArray();
|
||||
return resultArray;
|
||||
});
|
||||
if(this.filterCacheCount >= 2000) {
|
||||
// To prevent memory leak, we maintain an upper limit for cache size.
|
||||
|
||||
@@ -13,6 +13,18 @@ Filter function for [is[shadow]]
|
||||
Export our filter function
|
||||
*/
|
||||
exports.shadow = function(source,prefix,options) {
|
||||
// Fast path: when source is wiki.each (all real tiddlers), use shadow title list
|
||||
if(source === options.wiki.each && prefix !== "!") {
|
||||
// Return real tiddlers that are also shadow tiddlers (overridden shadows)
|
||||
var results = [],
|
||||
shadowTitles = options.wiki.allShadowTitles();
|
||||
for(var i = 0, len = shadowTitles.length; i < len; i++) {
|
||||
if(options.wiki.tiddlerExists(shadowTitles[i])) {
|
||||
results.push(shadowTitles[i]);
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
var results = [];
|
||||
if(prefix === "!") {
|
||||
source(function(tiddler,title) {
|
||||
|
||||
@@ -13,6 +13,14 @@ Filter function for [is[system]]
|
||||
Export our filter function
|
||||
*/
|
||||
exports.system = function(source,prefix,options) {
|
||||
// Fast path: when iterating all tiddlers, use pre-partitioned arrays
|
||||
if(source === options.wiki.each) {
|
||||
if(prefix === "!") {
|
||||
return options.wiki.allNonSystemTitles();
|
||||
} else {
|
||||
return options.wiki.allSystemTitles();
|
||||
}
|
||||
}
|
||||
var results = [];
|
||||
if(prefix === "!") {
|
||||
source(function(tiddler,title) {
|
||||
|
||||
@@ -13,6 +13,13 @@ Filter function for [is[tiddler]]
|
||||
Export our filter function
|
||||
*/
|
||||
exports.tiddler = function(source,prefix,options) {
|
||||
// Fast path: wiki.each only iterates real tiddlers, all of which exist
|
||||
if(source === options.wiki.each) {
|
||||
if(prefix === "!") {
|
||||
return []; // No real tiddler fails tiddlerExists
|
||||
}
|
||||
return source; // Return iterator directly; all real tiddlers pass
|
||||
}
|
||||
var results = [];
|
||||
if(prefix === "!") {
|
||||
source(function(tiddler,title) {
|
||||
|
||||
@@ -13,6 +13,21 @@ Filter operator returning all the tags of the selected tiddlers
|
||||
Export our filter function
|
||||
*/
|
||||
exports.tags = function(source,operator,options) {
|
||||
// Fast path: cache result when iterating all tiddlers
|
||||
if(source === options.wiki.each) {
|
||||
return options.wiki.getGlobalCache("filter-tags-all-tiddlers",function() {
|
||||
var tags = {};
|
||||
source(function(tiddler,title) {
|
||||
var t, length;
|
||||
if(tiddler && tiddler.fields.tags) {
|
||||
for(t=0, length=tiddler.fields.tags.length; t<length; t++) {
|
||||
tags[tiddler.fields.tags[t]] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
return Object.keys(tags);
|
||||
});
|
||||
}
|
||||
var tags = {};
|
||||
source(function(tiddler,title) {
|
||||
var t, length;
|
||||
|
||||
@@ -59,14 +59,48 @@ LinkedList.prototype.push = function(/* values */) {
|
||||
LinkedList.prototype.pushTop = function(value) {
|
||||
var t;
|
||||
if($tw.utils.isArray(value)) {
|
||||
for(t=0; t<value.length; t++) {
|
||||
_assertString(value[t]);
|
||||
}
|
||||
for(t=0; t<value.length; t++) {
|
||||
_removeOne(this,value[t]);
|
||||
}
|
||||
for(t=0; t<value.length; t++) {
|
||||
_linkToEnd(this,value[t]);
|
||||
if(this.length === 0) {
|
||||
// Fast path for empty list: skip removal pass
|
||||
for(t = 0; t < value.length; t++) {
|
||||
_assertString(value[t]);
|
||||
}
|
||||
var prev = null,
|
||||
useInline = true;
|
||||
for(t = 0; t < value.length; t++) {
|
||||
if(useInline) {
|
||||
var v = value[t];
|
||||
var old = this.next.get(v);
|
||||
if(old !== undefined) {
|
||||
// Duplicate found: switch to _linkToEnd for this and all remaining elements
|
||||
useInline = false;
|
||||
_linkToEnd(this,v);
|
||||
} else {
|
||||
// Inline the common case of _linkToEnd for new unique values
|
||||
this.next.set(v,null);
|
||||
this.prev.set(v,prev);
|
||||
if(prev !== null) {
|
||||
this.next.set(prev,v);
|
||||
} else {
|
||||
this.next.set(null,v);
|
||||
}
|
||||
this.prev.set(null,v);
|
||||
this.length++;
|
||||
prev = v;
|
||||
}
|
||||
} else {
|
||||
_linkToEnd(this,value[t]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for(t=0; t<value.length; t++) {
|
||||
_assertString(value[t]);
|
||||
}
|
||||
for(t=0; t<value.length; t++) {
|
||||
_removeOne(this,value[t]);
|
||||
}
|
||||
for(t=0; t<value.length; t++) {
|
||||
_linkToEnd(this,value[t]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_assertString(value);
|
||||
|
||||
+33
-6
@@ -1400,15 +1400,15 @@ exports.search = function(text,options) {
|
||||
fields.push("text");
|
||||
}
|
||||
// Function to check a given tiddler for the search term
|
||||
var searchTiddler = function(title) {
|
||||
var searchTiddler = function(tiddler,title) {
|
||||
if(!searchTermsRegExps) {
|
||||
return true;
|
||||
}
|
||||
var notYetFound = searchTermsRegExps.slice();
|
||||
|
||||
var tiddler = self.getTiddler(title);
|
||||
if(!tiddler) {
|
||||
tiddler = new $tw.Tiddler({title: title, text: "", type: "text/vnd.tiddlywiki"});
|
||||
tiddler = self.getTiddler(title);
|
||||
if(!tiddler) {
|
||||
tiddler = new $tw.Tiddler({title: title, text: "", type: "text/vnd.tiddlywiki"});
|
||||
}
|
||||
}
|
||||
var contentTypeInfo = $tw.config.contentTypeInfo[tiddler.fields.type] || $tw.config.contentTypeInfo["text/vnd.tiddlywiki"],
|
||||
searchFields;
|
||||
@@ -1424,6 +1424,33 @@ exports.search = function(text,options) {
|
||||
} else {
|
||||
searchFields = fields;
|
||||
}
|
||||
// Fast path for single search term (avoids array slice/splice per tiddler)
|
||||
if(searchTermsRegExps.length === 1) {
|
||||
var singleRegExp = searchTermsRegExps[0];
|
||||
for(var fieldIndex=0; fieldIndex<searchFields.length; fieldIndex++) {
|
||||
var fieldName = searchFields[fieldIndex];
|
||||
if(fieldName === "text" && contentTypeInfo.encoding !== "utf8") {
|
||||
continue;
|
||||
}
|
||||
var str = tiddler.fields[fieldName];
|
||||
if(str) {
|
||||
if($tw.utils.isArray(str)) {
|
||||
for(var s=0; s<str.length; s++) {
|
||||
if(singleRegExp.test(str[s])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
str = tiddler.getFieldString(fieldName);
|
||||
if(singleRegExp.test(str)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
var notYetFound = searchTermsRegExps.slice();
|
||||
for(var fieldIndex=0; notYetFound.length>0 && fieldIndex<searchFields.length; fieldIndex++) {
|
||||
// Don't search the text field if the content type is binary
|
||||
var fieldName = searchFields[fieldIndex];
|
||||
@@ -1463,7 +1490,7 @@ exports.search = function(text,options) {
|
||||
var results = [],
|
||||
source = options.source || this.each;
|
||||
source(function(tiddler,title) {
|
||||
if(searchTiddler(title) !== invert) {
|
||||
if(searchTiddler(tiddler,title) !== invert) {
|
||||
results.push(title);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user