Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Execute: simplify + speedup #1286

Merged
merged 7 commits into from
Apr 18, 2018
Merged

Conversation

IvanGoncharov
Copy link
Member

@IvanGoncharov IvanGoncharov commented Mar 13, 2018

For a feature, I'm working on I needed to understand how execute is working so I read through its implementation. I notice that completeValueWithLocatedError is called only from completeValueCatchingError and would be simpler to just merge them together.
So main reason of this PR is to remove one layout of try/catch & isPromise/then and make error handling more obvious.

To check that I didn't introduce any performance regressions I profiled modified version of introspectionFromSchema-benchmark.js
During this profiling I discovered that hottest function is a fat arrow callback inside executeFields function:

Statistical profiling result from base3-v8.log, (9005 ticks, 439 unaccounted, 0 excluded).

 [Shared libraries]:
   ticks  total  nonlib   name
      9    0.1%          /usr/lib/system/libsystem_pthread.dylib
      2    0.0%          /usr/lib/system/libsystem_malloc.dylib

 [JavaScript]:
   ticks  total  nonlib   name
    892    9.9%    9.9%  LoadIC: A load IC from the snapshot
    850    9.4%    9.5%  LazyCompile: *<anonymous> /Users/ivang/dev/graphql-js/perf_test/baseDist/execution/execute.js:361:58
    436    4.8%    4.8%  LazyCompile: *completeValue /Users/ivang/dev/graphql-js/perf_test/baseDist/execution/execute.js:613:23
    345    3.8%    3.8%  Stub: CallApiGetterStub
    259    2.9%    2.9%  LazyCompile: *getArgumentValues /Users/ivang/dev/graphql-js/perf_test/baseDist/execution/values.js:101:27
    248    2.8%    2.8%  LazyCompile: *completeValueWithLocatedError /Users/ivang/dev/graphql-js/perf_test/baseDist/execution/execute.js:578:39
    231    2.6%    2.6%  Builtin: KeyedLoadIC_Megamorphic
    210    2.3%    2.3%  Builtin: ArrayReduce
    202    2.2%    2.2%  Stub: GetPropertyStub
    130    1.4%    1.4%  LazyCompile: *defaultFieldResolver /Users/ivang/dev/graphql-js/perf_test/baseDist/execution/execute.js:844:88

So I replaced Array.reduce with for cycle on Object.keys and together with removal of completeValueWithLocatedError it resulted in ~15% speedup(9005 ticks vs 7612 ticks), see profile:

Statistical profiling result from changed_beautiful-v8.log, (7612 ticks, 402 unaccounted, 0 excluded).

 [Shared libraries]:
   ticks  total  nonlib   name
      9    0.1%          /usr/lib/system/libsystem_pthread.dylib
      1    0.0%          /usr/lib/system/libsystem_platform.dylib
      1    0.0%          /usr/lib/system/libsystem_malloc.dylib

 [JavaScript]:
   ticks  total  nonlib   name
    823   10.8%   10.8%  LoadIC: A load IC from the snapshot
    551    7.2%    7.2%  LazyCompile: *completeObjectValue /Users/ivang/dev/graphql-js/perf_test/changedDist/execution/execute.js:732:29
    477    6.3%    6.3%  LazyCompile: *completeValue /Users/ivang/dev/graphql-js/perf_test/changedDist/execution/execute.js:611:23
    335    4.4%    4.4%  Stub: CallApiGetterStub
    279    3.7%    3.7%  LazyCompile: *getArgumentValues /Users/ivang/dev/graphql-js/perf_test/changedDist/execution/values.js:101:27
    247    3.2%    3.2%  Builtin: KeyedLoadIC_Megamorphic
    145    1.9%    1.9%  Stub: GetPropertyStub
    121    1.6%    1.6%  LazyCompile: *defaultFieldResolver /Users/ivang/dev/graphql-js/perf_test/changedDist/execution/execute.js:835:88
    100    1.3%    1.3%  LazyCompile: *getFieldDef /Users/ivang/dev/graphql-js/perf_test/changedDist/execution/execute.js:855:21
     91    1.2%    1.2%  Builtin: KeyedStoreIC_Megamorphic_Strict
     88    1.2%    1.2%  Builtin: FastNewClosure
     77    1.0%    1.0%  Builtin: ArrayReduce
     75    1.0%    1.0%  Builtin: WeakMapLookupHashIndex

Performance improvement achieved on a synthetic test and probably wouldn't account for any measurable speedup in most servers. Another small bonus is call stack reduction 1 call (2 calls if a value returned as a promise) for every field or item inside an array.

But most importantly this PR make it easier for a developer to trace how values are resolved/complete inside execute.

@@ -904,13 +871,6 @@ function completeValue(
path: ResponsePath,
result: mixed,
): MaybePromise<mixed> {
// If result is a Promise, apply-lift over completeValue.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we have a test that shows completing Array<Promise> works?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leebyron Yes, it checked here:

describe('Array<Promise<T>>', () => {
it(
'Contains values',
check(type, [resolved(1), resolved(2)], {
data: { nest: { test: [1, 2] } },
}),
);

return null;
} catch (rawError) {
const error = locatedFieldError(rawError, fieldNodes, path);
return handleFieldError(error, returnType, exeContext);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

locatedFieldError and handleFieldError are always called together - should they become a single function?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leebyron Yes, you're right. I merged them together 👍

result,
);
let completed;
if (isPromise(result)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned moving this out of completeValue may break some scenarios we might not have sufficient test coverage over.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@leebyron completeValue only called in 3 places:

// If result is a Promise, apply-lift over completeValue.
if (isPromise(result)) {
return result.then(resolved =>
completeValue(exeContext, returnType, fieldNodes, info, path, resolved),
);
}

I moved this code to completeValueCatchingError so nothing changed here.

// If field type is NonNull, complete for inner type, and throw field error
// if result is null.
if (isNonNullType(returnType)) {
const completed = completeValue(
exeContext,
returnType.ofType,
fieldNodes,
info,
path,
result,
);

This code is called after promise uplift so nothing changed here.

const completed = completeValue(

Also nothing is broken here since this call moved to completeValueCatchingError and completeValueCatchingError now handles promise uplift by itself.

@IvanGoncharov
Copy link
Member Author

@leebyron I address review comments. Can you please take a second look?

@leebyron leebyron merged commit 69d90c6 into graphql:master Apr 18, 2018
@leebyron
Copy link
Contributor

Really nice work simplifying this!

IvanGoncharov added a commit to IvanGoncharov/graphql-js that referenced this pull request Apr 18, 2018
leebyron pushed a commit that referenced this pull request Apr 18, 2018
* Simplify non-null tests

* Remove 'check' function and write test directly

* containSubset => deep.equal

* Change order of errors because of #1286

* remove excessive async/await
@IvanGoncharov IvanGoncharov deleted the executeSpeedup branch June 6, 2018 15:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants