From c76c2e71f01a61a9b11001b0dc3fe837975f41c0 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 17 Nov 2023 14:44:53 -0700 Subject: [PATCH] rustdoc-search: fast path for 1-query unification Short queries, in addition to being common, are also the base case for a lot of more complicated queries. We can avoid most of the backtracking data structures, and use simple recursive matching instead, by special casing them. Profile output: https://notriddle.com/rustdoc-html-demo-5/profile-3/index.html --- src/librustdoc/html/static/js/search.js | 78 ++++++++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/src/librustdoc/html/static/js/search.js b/src/librustdoc/html/static/js/search.js index 0c337f47dc9..38391436aea 100644 --- a/src/librustdoc/html/static/js/search.js +++ b/src/librustdoc/html/static/js/search.js @@ -1318,7 +1318,7 @@ function initSearch(rawSearchIndex) { * then this function will try with a different solution, or bail with false if it * runs out of candidates. * - * @param {Array} fnTypes - The objects to check. + * @param {Array} fnTypesIn - The objects to check. * @param {Array} queryElems - The elements from the parsed query. * @param {[FunctionType]} whereClause - Trait bounds for generic items. * @param {Map|null} mgensIn @@ -1340,6 +1340,79 @@ function initSearch(rawSearchIndex) { } const ql = queryElems.length; let fl = fnTypesIn.length; + + // Fast path + if (queryElems.length === 1 && queryElems[0].generics.length === 0) { + const queryElem = queryElems[0]; + for (const fnType of fnTypesIn) { + if (!unifyFunctionTypeIsMatchCandidate(fnType, queryElem, whereClause, mgens)) { + continue; + } + if (fnType.id < 0 && queryElem.id < 0) { + if (mgens === null) { + mgens = new Map(); + } + const alreadyAssigned = mgens.has(fnType.id); + if (alreadyAssigned) { + if (mgens.get(fnType.id) !== queryElem.id) { + continue; + } + } else { + mgens.set(fnType.id, queryElem.id); + } + if (!solutionCb || solutionCb(mgens)) { + return true; + } + if (!alreadyAssigned) { + mgens.delete(fnType.id); + } + } else if (!solutionCb || solutionCb(mgens)) { + // unifyFunctionTypeIsMatchCandidate already checks that ids match + return true; + } + } + for (const fnType of fnTypesIn) { + if (!unifyFunctionTypeIsUnboxCandidate(fnType, queryElem, whereClause, mgens)) { + continue; + } + if (fnType.id < 0) { + if (mgens === null) { + mgens = new Map(); + } + const alreadyAssigned = mgens.has(fnType.id); + if (alreadyAssigned) { + if (mgens.get(fnType.id) !== 0) { + continue; + } + } else { + mgens.set(fnType.id, 0); + } + if (unifyFunctionTypes( + whereClause[(-fnType.id) - 1], + queryElems, + whereClause, + mgens, + solutionCb + )) { + return true; + } + if (!alreadyAssigned) { + mgens.delete(fnType.id); + } + } else if (unifyFunctionTypes( + fnType.generics, + queryElems, + whereClause, + mgens, + solutionCb + )) { + return true; + } + } + return false; + } + + // Slow path /** * @type Array */ @@ -1405,7 +1478,8 @@ function initSearch(rawSearchIndex) { if (fnType.id < 0) { if (mgens === null) { mgens = new Map(); - } else if (mgens.has(fnType.id) && mgens.get(fnType.id) !== queryElem.id) { + } else if (mgens.has(fnType.id) && + mgens.get(fnType.id) !== queryElem.id) { continue; } mgens.set(fnType.id, queryElem.id);