Skip to content

Commit 10e31f7

Browse files
authored
Merge pull request rmosolgo#4338 from rmosolgo/analysis-perf
Eliminate dynamic dispatch from Language::Visitor and Analysis::AST::Visitor
2 parents 1a77570 + b1db3b1 commit 10e31f7

5 files changed

Lines changed: 286 additions & 151 deletions

File tree

benchmark/run.rb

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -146,18 +146,26 @@ def self.profile_large_analysis
146146
query_str << " field#{f}(arg: \"a\")\n"
147147
end
148148
query_str << " selfField { selfField { selfField { __typename } } }\n"
149-
query_str << " int0Field { ...Int0Fields }"
149+
# query_str << " int0Field { ...Int0Fields }"
150150
query_str << "}\n"
151151
end
152-
query_str << "fragment Int0Fields on Interface0 { __typename }"
152+
# query_str << "fragment Int0Fields on Interface0 { __typename }"
153153
query = GraphQL::Query.new(SILLY_LARGE_SCHEMA, query_str)
154-
analyzers = [GraphQL::Analysis::AST::FieldUsage]
154+
analyzers = [
155+
GraphQL::Analysis::AST::FieldUsage,
156+
GraphQL::Analysis::AST::QueryDepth,
157+
GraphQL::Analysis::AST::QueryComplexity
158+
]
155159
Benchmark.ips do |x|
156160
x.report("Running introspection") {
157161
GraphQL::Analysis::AST.analyze_query(query, analyzers)
158162
}
159163
end
160164

165+
StackProf.run(mode: :wall, out: "last-stackprof.dump", interval: 1) do
166+
GraphQL::Analysis::AST.analyze_query(query, analyzers)
167+
end
168+
161169
result = StackProf.run(mode: :wall, interval: 1) do
162170
GraphQL::Analysis::AST.analyze_query(query, analyzers)
163171
end

lib/graphql/analysis/ast/visitor.rb

Lines changed: 42 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,41 @@ def response_path
6565
end
6666

6767
# Visitor Hooks
68+
[
69+
:operation_definition, :fragment_definition,
70+
:inline_fragment, :field, :directive, :argument, :fragment_spread
71+
].each do |node_type|
72+
module_eval <<-RUBY, __FILE__, __LINE__
73+
def call_on_enter_#{node_type}(node, parent)
74+
@analyzers.each do |a|
75+
begin
76+
a.on_enter_#{node_type}(node, parent, self)
77+
rescue AnalysisError => err
78+
@rescued_errors << err
79+
end
80+
end
81+
end
82+
83+
def call_on_leave_#{node_type}(node, parent)
84+
@analyzers.each do |a|
85+
begin
86+
a.on_leave_#{node_type}(node, parent, self)
87+
rescue AnalysisError => err
88+
@rescued_errors << err
89+
end
90+
end
91+
end
92+
93+
RUBY
94+
end
6895

6996
def on_operation_definition(node, parent)
7097
object_type = @schema.root_type_for_operation(node.operation_type)
7198
@object_types.push(object_type)
7299
@path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
73-
call_analyzers(:on_enter_operation_definition, node, parent)
100+
call_on_enter_operation_definition(node, parent)
74101
super
75-
call_analyzers(:on_leave_operation_definition, node, parent)
102+
call_on_leave_operation_definition(node, parent)
76103
@object_types.pop
77104
@path.pop
78105
end
@@ -81,19 +108,19 @@ def on_fragment_definition(node, parent)
81108
on_fragment_with_type(node) do
82109
@path.push("fragment #{node.name}")
83110
@in_fragment_def = false
84-
call_analyzers(:on_enter_fragment_definition, node, parent)
111+
call_on_enter_fragment_definition(node, parent)
85112
super
86113
@in_fragment_def = false
87-
call_analyzers(:on_leave_fragment_definition, node, parent)
114+
call_on_leave_fragment_definition(node, parent)
88115
end
89116
end
90117

91118
def on_inline_fragment(node, parent)
92119
on_fragment_with_type(node) do
93120
@path.push("...#{node.type ? " on #{node.type.name}" : ""}")
94-
call_analyzers(:on_enter_inline_fragment, node, parent)
121+
call_on_enter_inline_fragment(node, parent)
95122
super
96-
call_analyzers(:on_leave_inline_fragment, node, parent)
123+
call_on_leave_inline_fragment(node, parent)
97124
end
98125
end
99126

@@ -114,12 +141,10 @@ def on_field(node, parent)
114141
@skipping = @skip_stack.last || skip?(node)
115142
@skip_stack << @skipping
116143

117-
call_analyzers(:on_enter_field, node, parent)
144+
call_on_enter_field(node, parent)
118145
super
119-
120146
@skipping = @skip_stack.pop
121-
122-
call_analyzers(:on_leave_field, node, parent)
147+
call_on_leave_field(node, parent)
123148
@response_path.pop
124149
@field_definitions.pop
125150
@object_types.pop
@@ -129,9 +154,9 @@ def on_field(node, parent)
129154
def on_directive(node, parent)
130155
directive_defn = @schema.directives[node.name]
131156
@directive_definitions.push(directive_defn)
132-
call_analyzers(:on_enter_directive, node, parent)
157+
call_on_enter_directive(node, parent)
133158
super
134-
call_analyzers(:on_leave_directive, node, parent)
159+
call_on_leave_directive(node, parent)
135160
@directive_definitions.pop
136161
end
137162

@@ -153,29 +178,23 @@ def on_argument(node, parent)
153178

154179
@argument_definitions.push(argument_defn)
155180
@path.push(node.name)
156-
call_analyzers(:on_enter_argument, node, parent)
181+
call_on_enter_argument(node, parent)
157182
super
158-
call_analyzers(:on_leave_argument, node, parent)
183+
call_on_leave_argument(node, parent)
159184
@argument_definitions.pop
160185
@path.pop
161186
end
162187

163188
def on_fragment_spread(node, parent)
164189
@path.push("... #{node.name}")
165-
call_analyzers(:on_enter_fragment_spread, node, parent)
190+
call_on_enter_fragment_spread(node, parent)
166191
enter_fragment_spread_inline(node)
167192
super
168193
leave_fragment_spread_inline(node)
169-
call_analyzers(:on_leave_fragment_spread, node, parent)
194+
call_on_leave_fragment_spread(node, parent)
170195
@path.pop
171196
end
172197

173-
def on_abstract_node(node, parent)
174-
call_analyzers(:on_enter_abstract_node, node, parent)
175-
super
176-
call_analyzers(:on_leave_abstract_node, node, parent)
177-
end
178-
179198
# @return [GraphQL::BaseType] The current object type
180199
def type_definition
181200
@object_types.last
@@ -226,9 +245,7 @@ def enter_fragment_spread_inline(fragment_spread)
226245

227246
object_types << object_type
228247

229-
fragment_def.selections.each do |selection|
230-
visit_node(selection, fragment_def)
231-
end
248+
on_fragment_definition_children(fragment_def)
232249
end
233250

234251
# Visit a fragment spread inline instead of visiting the definition
@@ -242,16 +259,6 @@ def skip?(ast_node)
242259
dir.any? && !GraphQL::Execution::DirectiveChecks.include?(dir, query)
243260
end
244261

245-
def call_analyzers(method, node, parent)
246-
@analyzers.each do |analyzer|
247-
begin
248-
analyzer.public_send(method, node, parent, self)
249-
rescue AnalysisError => err
250-
@rescued_errors << err
251-
end
252-
end
253-
end
254-
255262
def on_fragment_with_type(node)
256263
object_type = if node.type
257264
@query.warden.get_type(node.type.name)

lib/graphql/language/nodes.rb

Lines changed: 38 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,19 @@ def visit_method
148148
149149
class << self
150150
attr_accessor :children_method_name
151+
152+
def visit_method
153+
:on_#{name_underscored}
154+
end
151155
end
152156
self.children_method_name = :#{name_underscored}s
153157
RUBY
154158
end
155159

160+
def children_of_type
161+
@children_methods
162+
end
163+
156164
private
157165

158166
# Name accessors which return lists of nodes,
@@ -300,7 +308,7 @@ class Argument < AbstractNode
300308
# @return [String] the key for this argument
301309

302310
# @!attribute value
303-
# @return [String, Float, Integer, Boolean, Array, InputObject] The value passed for this key
311+
# @return [String, Float, Integer, Boolean, Array, InputObject, VariableIdentifier] The value passed for this key
304312

305313
def children
306314
@children ||= Array(value).flatten.select { |v| v.is_a?(AbstractNode) }
@@ -325,35 +333,6 @@ class DirectiveDefinition < AbstractNode
325333
)
326334
end
327335

328-
# This is the AST root for normal queries
329-
#
330-
# @example Deriving a document by parsing a string
331-
# document = GraphQL.parse(query_string)
332-
#
333-
# @example Creating a string from a document
334-
# document.to_query_string
335-
# # { ... }
336-
#
337-
# @example Creating a custom string from a document
338-
# class VariableScrubber < GraphQL::Language::Printer
339-
# def print_argument(arg)
340-
# "#{arg.name}: <HIDDEN>"
341-
# end
342-
# end
343-
#
344-
# document.to_query_string(printer: VariableScrubber.new)
345-
#
346-
class Document < AbstractNode
347-
scalar_methods false
348-
children_methods(definitions: nil)
349-
# @!attribute definitions
350-
# @return [Array<OperationDefinition, FragmentDefinition>] top-level GraphQL units: operations or fragments
351-
352-
def slice_definition(name)
353-
GraphQL::Language::DefinitionSlice.slice(self, name)
354-
end
355-
end
356-
357336
# An enum value. The string is available as {#name}.
358337
class Enum < NameOnlyNode
359338
end
@@ -526,6 +505,35 @@ class OperationDefinition < AbstractNode
526505
self.children_method_name = :definitions
527506
end
528507

508+
# This is the AST root for normal queries
509+
#
510+
# @example Deriving a document by parsing a string
511+
# document = GraphQL.parse(query_string)
512+
#
513+
# @example Creating a string from a document
514+
# document.to_query_string
515+
# # { ... }
516+
#
517+
# @example Creating a custom string from a document
518+
# class VariableScrubber < GraphQL::Language::Printer
519+
# def print_argument(arg)
520+
# "#{arg.name}: <HIDDEN>"
521+
# end
522+
# end
523+
#
524+
# document.to_query_string(printer: VariableScrubber.new)
525+
#
526+
class Document < AbstractNode
527+
scalar_methods false
528+
children_methods(definitions: nil)
529+
# @!attribute definitions
530+
# @return [Array<OperationDefinition, FragmentDefinition>] top-level GraphQL units: operations or fragments
531+
532+
def slice_definition(name)
533+
GraphQL::Language::DefinitionSlice.slice(self, name)
534+
end
535+
end
536+
529537
# A type name, used for variable definitions
530538
class TypeName < NameOnlyNode
531539
end

0 commit comments

Comments
 (0)