-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Improved access speed of isPossibleType #4040
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
Conversation
Test Results 323 files 323 suites 2m 53s ⏱️ Results for commit d0cbc63. ♻️ This comment has been updated with latest results. |
| } | ||
| } | ||
| return mapBuilder.build(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This now pre-computes the interface -> ImplementingTypeDefinition s at registry build time and hence later in the schema type checking stage, the repeated ask for interface information is much faster
| */ | ||
| public boolean hasType(String name) { | ||
| return types.containsKey(name) || ScalarInfo.GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS.containsKey(name) || scalarTypes.containsKey(name) || objectTypeExtensions.containsKey(name); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
extra helper by string name - this will be lighter in lookup terms later on
| public Optional<TypeDefinition> getType(Type type) { | ||
| String typeName = TypeInfo.typeInfo(type).getName(); | ||
| return getType(typeName); | ||
| return getType(typeName(type)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TypeInfo.typeInfo(type).getName() allocates a TypeInfo object in order to just get a single attribute of it - the name of the type (and not its null wrapping etc...)
So this is more light weight and we avoid an extra allocation
This is then repeated in all the getType lookup methods
| * @return an optional {@link TypeDefinition} or empty if it's not found | ||
| */ | ||
| public Optional<TypeDefinition> getType(String typeName) { | ||
| return Optional.ofNullable(getTypeOrNull(typeName)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The older Optional returning methods are left in place but the new getTypeOrNull are more efficient - not need for a Optional allocation in order to know if something is present or not
So all the other inner code that used Optional<T> getType now used T getTypeOrNull variants
| return typeDefinition; | ||
| } | ||
| return Optional.empty(); | ||
| return null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is cheaper - no Optional allocated
| return definition instanceof UnionTypeDefinition || definition instanceof InterfaceTypeDefinition; | ||
| TypeDefinition typeDefinition = getTypeOrNull(type); | ||
| if (typeDefinition != null) { | ||
| return typeDefinition instanceof UnionTypeDefinition || typeDefinition instanceof InterfaceTypeDefinition; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See now we use the cheaper getTypeOrNull methods internally
| return definition instanceof ObjectTypeDefinition || definition instanceof InterfaceTypeDefinition; | ||
| TypeDefinition typeDefinition = getTypeOrNull(type); | ||
| if (typeDefinition != null) { | ||
| return typeDefinition instanceof ObjectTypeDefinition || typeDefinition instanceof InterfaceTypeDefinition; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cheaper
| */ | ||
| public boolean isObjectType(Type type) { | ||
| return getType(type, ObjectTypeDefinition.class).isPresent(); | ||
| return getTypeOrNull(type, ObjectTypeDefinition.class) != null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cheaper
| getAllImplementationsOf(targetInterface), | ||
| typeDefinition -> typeDefinition instanceof ObjectTypeDefinition, | ||
| typeDefinition -> (ObjectTypeDefinition) typeDefinition | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unwound the .stream() and replaced it with a cheaper immutable builder version. This is known to be faster
| TypeDefinition targetObjectTypeDef = getType(possibleType).get(); | ||
| TypeDefinition abstractTypeDef = getType(abstractType).get(); | ||
| TypeDefinition targetObjectTypeDef = Objects.requireNonNull(getTypeOrNull(possibleType)); | ||
| TypeDefinition abstractTypeDef = Objects.requireNonNull(getTypeOrNull(abstractType)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
required for nullaway now that I marked a few things
| if (checkType.isPresent()) { | ||
| if (checkType.get().getName().equals(targetObjectTypeDef.getName())) { | ||
| ObjectTypeDefinition checkType = getTypeOrNull(memberType, ObjectTypeDefinition.class); | ||
| if (checkType != null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cheaper
| } | ||
| } | ||
| return (TypeName) type; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is take from @xuorig s code in his PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is cheaper than allocating a TypeInfo object just to get its name
| */ | ||
| public static String typeName(Type<?> type) { | ||
| return getTypeName(type).getName(); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a version that is just the string of the TypeName - easier to use in map lookups
| allImplementationsOf.collect({ it.getName() }).every { it.startsWith("DT") } | ||
|
|
||
| implementationsOf.size() == 5 | ||
| implementationsOf.collect({ it.getName() }).every { it.startsWith("DT") } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just to make sure we did what we thought we would do and I can debug and confirm the pre-built map is as expected and its going to be cheaper when a full schema build is done and repeated calls are made on it to check interfaces and types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
However I killed the cache code but I figured this code is still valid
|
|
||
| static TypeDefinitionRegistry parseSDL(String spec) { | ||
| new SchemaParser().parse(spec) | ||
| new SchemaParser().parse(spec).readOnly() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more real - in the production code the registry is immutable read only mode
| where: | ||
| typeOfReg | _ | ||
| "mutable" | _ | ||
| "immutable" | _ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test both variations work as expected
| where: | ||
| typeOfReg | _ | ||
| "mutable" | _ | ||
| "immutable" | _ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test both variations work as expected
| where: | ||
| typeOfReg | _ | ||
| "mutable" | _ | ||
| "immutable" | _ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
test both variations work as expected
Yeah not quite, at least not for this particular issue. |
|
|
||
| ./gradlew clean jmhJar | ||
|
|
||
| java -jar "$JAR" "$@" No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A little helper to make it easier to run the JMH tests on the command line
| f1 : String | ||
| } | ||
| */ | ||
| private static String mkSDL() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a benchmark with lots of extends type X and interfaces
| if (!typeRegistry.hasType(unwrapped)) { | ||
| String name = TypeInfo.typeName(t); | ||
| if (!typeRegistry.hasType(name)) { | ||
| TypeName unwrapped = TypeInfo.typeInfo(t).getTypeName(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
faster look up - we only create a TypeInfo if its in error
| if (!typeRegistry.hasType(unwrapped)) { | ||
| String name = TypeInfo.typeName(ivType); | ||
| if (!typeRegistry.hasType(name)) { | ||
| TypeName unwrapped = TypeInfo.typeInfo(ivType).getTypeName(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
faster look up - we only create a TypeInfo if its in error
| if (interfaceTypeDef.getName().equals(targetInterface.getName())) { | ||
| return true; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
moved away from .stream() to the faster immutable code
| } | ||
| } | ||
| } | ||
| return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is @xuorig s code from his PR in this place
| "[named!]" | "named" | ||
| "[named!]!" | "named" | ||
| "[[named!]!]" | "named" | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from @xuorig PR
|
Wow the benchmark improvement is super impressive. Thanks so much @xuorig ! |
| * | ||
| * @return an optional {@link TypeDefinition} or empty if it's not found | ||
| */ | ||
| public <T extends TypeDefinition> Optional<T> getType(String typeName, Class<T> ofType) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just return null instead using Optional? Saves also an object allocation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah .. I see you have it actually below ... how about just killing this method then?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how about just killing this method then?
I will deprecate them but I think they are still used in our code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are all now deprecated
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.Optional; | ||
| import java.util.Objects; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting rid of the Optional methods and using the getOrNull ones
| String typeName = TypeInfo.typeInfo(type).getName(); | ||
| return getType(typeName); | ||
| return Optional.ofNullable(getTypeOrNull(type)); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now deprecated - getTypeOrNull preferred
This builds on #4033 but it creates a cache in the ImmutableTypeRegistry so that we don't need to iterate all types on each call
@xuorig - This takes your PR and extends it more to have a reverse cache of interface -> implementing type def AND it uses your cheaper TypeInfo method in more places
This means that algo change you made in your PR is not really needed. Why? Because when we are in read only mode, we don't have to loop all types to find all implemeenting types of an interface, we did that once at build time for all interfaces. So later the type checking will be faster
I originally had a cache of interface to implementing types but it turns out its slower to eagerly build this cache than it is to run the code that used it so I brought @xuorig changes into this PR
So in benchmark terms
So overall things have gotten significantly faster in building large schemas with lots of interfaces and extends. in fact since @xuorig started his investigations and inspired changes, we are 10x faster on this benchmark
This PR does not add much more raw throughput BUT it will reduce memory used