Skip to content

Commit

Permalink
Merge pull request #4533 from rmosolgo/add-type-perf
Browse files Browse the repository at this point in the history
Improve performance of Schema::Addition
  • Loading branch information
rmosolgo authored Jun 27, 2023
2 parents 28bccb5 + d711c85 commit 1b44c85
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 40 deletions.
6 changes: 6 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ namespace :bench do
GraphQLBenchmark.profile_batch_loaders
end

desc "Run benchmarks on schema creation"
task :profile_boot do
prepare_benchmark
GraphQLBenchmark.profile_boot
end

desc "Check the memory footprint of a large schema"
task :profile_schema_memory_footprint do
prepare_benchmark
Expand Down
83 changes: 58 additions & 25 deletions benchmark/run.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,43 +92,76 @@ def self.profile
StackProf::Report.new(result).print_text
end

SILLY_LARGE_SCHEMA = Class.new(GraphQL::Schema) do
query_t = Class.new(GraphQL::Schema::Object) do
graphql_name("Query")
int_ts = 5.times.map do |i|
int_t = Module.new do
include GraphQL::Schema::Interface
graphql_name "Interface#{i}"
5.times do |n2|
field :"field#{n2}", String do
argument :arg, String
def self.build_large_schema
Class.new(GraphQL::Schema) do
query_t = Class.new(GraphQL::Schema::Object) do
graphql_name("Query")
int_ts = 5.times.map do |i|
int_t = Module.new do
include GraphQL::Schema::Interface
graphql_name "Interface#{i}"
5.times do |n2|
field :"field#{n2}", String do
argument :arg, String
end
end
end
field :"int_field_#{i}", int_t
int_t
end
field :"int_field_#{i}", int_t
int_t
end

100.times do |n|
obj_t = Class.new(GraphQL::Schema::Object) do
graphql_name("Object#{n}")
implements(*int_ts)
20.times do |n2|
field :"field#{n2}", String do
argument :arg, String
end
obj_ts = 100.times.map do |n|
obj_t = Class.new(GraphQL::Schema::Object) do
graphql_name("Object#{n}")
implements(*int_ts)
20.times do |n2|
field :"field#{n2}", String do
argument :arg, String
end

end
field :self_field, self
field :int_0_field, int_ts[0]
end
field :self_field, self
field :int_0_field, int_ts[0]

field :"rootfield#{n}", obj_t
obj_t
end

field :"rootfield#{n}", obj_t
10.times do |n|
union_t = Class.new(GraphQL::Schema::Union) do
graphql_name "Union#{n}"
possible_types(*obj_ts.sample(10))
end
field :"unionfield#{n}", union_t
end
end
query(query_t)
end
end

def self.profile_boot
Benchmark.ips do |x|
x.config(time: 10)
x.report("Booting large schema") {
build_large_schema
}
end

result = StackProf.run(mode: :wall, interval: 1) do
build_large_schema
end
query(query_t)
StackProf::Report.new(result).print_text

report = MemoryProfiler.report do
build_large_schema
end

report.pretty_print
end

SILLY_LARGE_SCHEMA = build_large_schema

def self.profile_large_introspection
schema = SILLY_LARGE_SCHEMA
Benchmark.ips do |x|
Expand Down
44 changes: 32 additions & 12 deletions lib/graphql/schema/addition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,21 @@ def get_local_type(name)
end

def add_directives_from(owner)
dirs = owner.directives.map(&:class)
@directives.merge(dirs)
add_type_and_traverse(dirs)
if (dir_instances = owner.directives).any?
dirs = dir_instances.map(&:class)
@directives.merge(dirs)
add_type_and_traverse(dirs)
end
end

def add_type_and_traverse(new_types)
late_types = []
new_types.each { |t| add_type(t, owner: nil, late_types: late_types, path: [t.graphql_name]) }
path = []
new_types.each do |t|
path.push(t.graphql_name)
add_type(t, owner: nil, late_types: late_types, path: path)
path.pop
end
missed_late_types = 0
while (late_type_vals = late_types.shift)
type_owner, lt = late_type_vals
Expand Down Expand Up @@ -158,7 +165,9 @@ def add_type(type, owner:, late_types:, path:)
type.all_argument_definitions.each do |arg|
arg_type = arg.type.unwrap
references_to(arg_type, from: arg)
add_type(arg_type, owner: arg, late_types: late_types, path: path + [arg.graphql_name])
path.push(arg.graphql_name)
add_type(arg_type, owner: arg, late_types: late_types, path: path)
path.pop
if arg.default_value?
@arguments_with_default_values << arg
end
Expand All @@ -179,48 +188,58 @@ def add_type(type, owner:, late_types:, path:)
name = field.graphql_name
field_type = field.type.unwrap
references_to(field_type, from: field)
field_path = path + [name]
add_type(field_type, owner: field, late_types: late_types, path: field_path)
path.push(name)
add_type(field_type, owner: field, late_types: late_types, path: path)
add_directives_from(field)
field.all_argument_definitions.each do |arg|
add_directives_from(arg)
arg_type = arg.type.unwrap
references_to(arg_type, from: arg)
add_type(arg_type, owner: arg, late_types: late_types, path: field_path + [arg.graphql_name])
path.push(arg.graphql_name)
add_type(arg_type, owner: arg, late_types: late_types, path: path)
path.pop
if arg.default_value?
@arguments_with_default_values << arg
end
end
path.pop
end
end
if type.kind.input_object?
type.all_argument_definitions.each do |arg|
add_directives_from(arg)
arg_type = arg.type.unwrap
references_to(arg_type, from: arg)
add_type(arg_type, owner: arg, late_types: late_types, path: path + [arg.graphql_name])
path.push(arg.graphql_name)
add_type(arg_type, owner: arg, late_types: late_types, path: path)
path.pop
if arg.default_value?
@arguments_with_default_values << arg
end
end
end
if type.kind.union?
@possible_types[type.graphql_name] = type.all_possible_types
path.push("possible_types")
type.all_possible_types.each do |t|
add_type(t, owner: type, late_types: late_types, path: path + ["possible_types"])
add_type(t, owner: type, late_types: late_types, path: path)
end
path.pop
end
if type.kind.interface?
path.push("orphan_types")
type.orphan_types.each do |t|
add_type(t, owner: type, late_types: late_types, path: path + ["orphan_types"])
add_type(t, owner: type, late_types: late_types, path: path)
end
path.pop
end
if type.kind.object?
possible_types_for_this_name = @possible_types[type.graphql_name] ||= []
possible_types_for_this_name << type
end

if type.kind.object? || type.kind.interface?
path.push("implements")
type.interface_type_memberships.each do |interface_type_membership|
case interface_type_membership
when Schema::TypeMembership
Expand All @@ -235,8 +254,9 @@ def add_type(type, owner:, late_types:, path:)
else
raise ArgumentError, "Invariant: unexpected type membership for #{type.graphql_name}: #{interface_type_membership.class} (#{interface_type_membership.inspect})"
end
add_type(interface_type, owner: type, late_types: late_types, path: path + ["implements"])
add_type(interface_type, owner: type, late_types: late_types, path: path)
end
path.pop
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/graphql/schema/field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ def initialize(type: nil, name: nil, owner: nil, null: nil, description: NOT_CON
self.validates(validates)
end

if definition_block
if block_given?
if definition_block.arity == 1
yield self
else
Expand Down
12 changes: 10 additions & 2 deletions lib/graphql/schema/member/build_type.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,14 @@ def to_type_name(something)
to_type_name(something.name)
end
when String
something.gsub(/\]\[\!/, "").split("::").last
if something.include?("]") ||
something.include?("[") ||
something.include?("!") ||
something.include?("::")
something.gsub(/\]\[\!/, "").split("::").last
else
something
end
when GraphQL::Schema::NonNull, GraphQL::Schema::List
to_type_name(something.unwrap)
else
Expand All @@ -122,7 +129,8 @@ def camelize(string)
return string unless string.include?("_")
camelized = string.split('_').each(&:capitalize!).join
camelized[0] = camelized[0].downcase
if (match_data = string.match(/\A(_+)/))
if string.start_with?("_")
match_data = string.match(/\A(_+)/)
camelized = "#{match_data[0]}#{camelized}"
end
camelized
Expand Down

0 comments on commit 1b44c85

Please sign in to comment.