Skip to content

Commit 4362223

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 5fec485 commit 4362223

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
@@ -1197,6 +1197,34 @@ function collectAndExecuteSubfields(
11971197
result: mixed,
11981198
): mixed {
11991199
// Collect sub-fields to execute to complete this value.
1200+
const subFieldNodes = collectSubfields(exeContext, returnType, fieldNodes);
1201+
return executeFields(exeContext, returnType, result, path, subFieldNodes);
1202+
}
1203+
1204+
/**
1205+
* A memoized collection of relevant subfields in the context of the return
1206+
* type. Memoizing ensures the subfields are not repeatedly calculated, which
1207+
* saves overhead when resolving lists of values.
1208+
*/
1209+
const subfieldCache: WeakMap<
1210+
$ReadOnlyArray<FieldNode>,
1211+
WeakMap<GraphQLObjectType, ObjMap<Array<FieldNode>>>,
1212+
> = new WeakMap();
1213+
function collectSubfields(
1214+
exeContext: ExecutionContext,
1215+
returnType: GraphQLObjectType,
1216+
fieldNodes: $ReadOnlyArray<FieldNode>,
1217+
): ObjMap<Array<FieldNode>> {
1218+
let cacheByReturnType = subfieldCache.get(fieldNodes);
1219+
if (cacheByReturnType) {
1220+
const cachedSubFieldNodes = cacheByReturnType.get(returnType);
1221+
if (cachedSubFieldNodes) {
1222+
return cachedSubFieldNodes;
1223+
}
1224+
} else {
1225+
cacheByReturnType = new WeakMap();
1226+
subfieldCache.set(fieldNodes, cacheByReturnType);
1227+
}
12001228
let subFieldNodes = Object.create(null);
12011229
const visitedFragmentNames = Object.create(null);
12021230
for (let i = 0; i < fieldNodes.length; i++) {
@@ -1211,8 +1239,8 @@ function collectAndExecuteSubfields(
12111239
);
12121240
}
12131241
}
1214-
1215-
return executeFields(exeContext, returnType, result, path, subFieldNodes);
1242+
cacheByReturnType.set(returnType, subFieldNodes);
1243+
return subFieldNodes;
12161244
}
12171245

12181246
/**

0 commit comments

Comments
 (0)