Skip to content

Commit 2eba17d

Browse files
committed
Perf: memoize collectSubfields
Collecting subfields occurs after resolving a field's value and before resolving subfield values. This step collects fragment spreads and checks inline fragment conditions. When fetching a list of things, this step is computed with the same inputs and expecting the same outputs for each item in the list. Memoizing ensures the work is done at most once per type returned from a list. I tested this against the introspection query (which is both synchronous and complex) against a large schema and saw a ~15%-25% reduction in runtime. In practice I don't expect most queries to see this level of speedup as most queries are limited by backend communication and not execution overhead.
1 parent 3848c75 commit 2eba17d

File tree

1 file changed

+30
-2
lines changed

1 file changed

+30
-2
lines changed

src/execution/execute.js

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,34 @@ function collectAndExecuteSubfields(
12361236
result: mixed,
12371237
): mixed {
12381238
// Collect sub-fields to execute to complete this value.
1239+
const subFieldNodes = collectSubfields(exeContext, returnType, fieldNodes);
1240+
return executeFields(exeContext, returnType, result, path, subFieldNodes);
1241+
}
1242+
1243+
/**
1244+
* A memoized collection of relevant subfields in the context of the return
1245+
* type. Memoizing ensures the subfields are not repeatedly calculated, which
1246+
* saves overhead when resolving lists of values.
1247+
*/
1248+
const subfieldCache: WeakMap<
1249+
$ReadOnlyArray<FieldNode>,
1250+
WeakMap<GraphQLObjectType, ObjMap<Array<FieldNode>>>,
1251+
> = new WeakMap();
1252+
function collectSubfields(
1253+
exeContext: ExecutionContext,
1254+
returnType: GraphQLObjectType,
1255+
fieldNodes: $ReadOnlyArray<FieldNode>,
1256+
): ObjMap<Array<FieldNode>> {
1257+
let cacheByReturnType = subfieldCache.get(fieldNodes);
1258+
if (cacheByReturnType) {
1259+
const cachedSubFieldNodes = cacheByReturnType.get(returnType);
1260+
if (cachedSubFieldNodes) {
1261+
return cachedSubFieldNodes;
1262+
}
1263+
} else {
1264+
cacheByReturnType = new WeakMap();
1265+
subfieldCache.set(fieldNodes, cacheByReturnType);
1266+
}
12391267
let subFieldNodes = Object.create(null);
12401268
const visitedFragmentNames = Object.create(null);
12411269
for (let i = 0; i < fieldNodes.length; i++) {
@@ -1250,8 +1278,8 @@ function collectAndExecuteSubfields(
12501278
);
12511279
}
12521280
}
1253-
1254-
return executeFields(exeContext, returnType, result, path, subFieldNodes);
1281+
cacheByReturnType.set(returnType, subFieldNodes);
1282+
return subFieldNodes;
12551283
}
12561284

12571285
/**

0 commit comments

Comments
 (0)