-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
* | ||
* @return true if the registry has a type by that name | ||
*/ | ||
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
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 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
@@ -569,7 +665,7 @@ public boolean isObjectTypeOrInterface(Type type) { | |||
* @return true if its an object type | |||
*/ | |||
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
@@ -32,7 +32,7 @@ import static java.lang.String.format | |||
class SchemaTypeCheckerTest extends Specification { | |||
|
|||
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" "$@" |
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
@@ -29,7 +29,7 @@ | |||
import java.util.HashMap; | |||
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
public Optional<TypeDefinition> getType(Type type) { | ||
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