Skip to content

Commit e5f0483

Browse files
committed
speed up Operation#call
We now have a separate Introsection class plus cache to introspect operations and to reuse introspection analysis. The test suite runs about 20% faster.
1 parent 56be235 commit e5f0483

File tree

2 files changed

+92
-66
lines changed

2 files changed

+92
-66
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* better `-` to `_` conversion [Nakilon]
99
* fix `GValue#set` for stricter metadata rules in 8.9 [jcupitt]
1010
* fix a ref leak on operation build error [jcupitt]
11+
* faster operation call -- test suite is ~20% quicker [jcupitt]
1112

1213
## Version 2.0.16 (2019-9-21)
1314

lib/vips/operation.rb

Lines changed: 91 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,78 @@ module Vips
3535

3636
attach_function :vips_operation_get_flags, [:pointer], :int
3737

38+
# Introspect a vips operation and return a large structure containing
39+
# everything we know about it. This is used for doc generation as well as
40+
# call.
41+
class Introspect
42+
attr_reader :name, :description, :flags, :args, :required_input,
43+
:optional_input, :required_output, :optional_output
44+
45+
@@introspect_cache = {}
46+
47+
def initialize name
48+
op = Operation.new name
49+
50+
@name = name
51+
@description = Vips::vips_object_get_description op
52+
@flags = op.get_flags
53+
@args = []
54+
@required_input = []
55+
@optional_input = {}
56+
@required_output = []
57+
@optional_output = {}
58+
59+
# find all the arguments the operator can take
60+
op.argument_map do |pspec, argument_class, _argument_instance|
61+
flags = argument_class[:flags]
62+
if (flags & ARGUMENT_CONSTRUCT) != 0
63+
# names can include - as punctuation, but we always use _ in
64+
# Ruby
65+
arg_name = pspec[:name].tr("-", "_")
66+
gtype = op.get_typeof arg_name
67+
blurb = GObject::g_param_spec_get_blurb pspec
68+
69+
@args << [arg_name, flags, gtype, blurb]
70+
end
71+
end
72+
73+
@args.each do |details|
74+
arg_name, flags, gtype, blurb = details
75+
76+
next if (flags & ARGUMENT_DEPRECATED) != 0
77+
78+
if (flags & ARGUMENT_INPUT) != 0
79+
if (flags & ARGUMENT_REQUIRED) != 0
80+
@required_input << details
81+
else
82+
@optional_input[arg_name] = details
83+
end
84+
end
85+
86+
# MODIFY INPUT args count as OUTPUT as well
87+
if (flags & ARGUMENT_OUTPUT) != 0 ||
88+
((flags & ARGUMENT_INPUT) != 0 &&
89+
(flags & ARGUMENT_MODIFY) != 0)
90+
if (flags & ARGUMENT_REQUIRED) != 0
91+
@required_output << details
92+
else
93+
@optional_output[arg_name] = details
94+
end
95+
end
96+
end
97+
98+
end
99+
100+
def self.get name
101+
if ! @@introspect_cache.include? name
102+
@@introspect_cache[name] = Introspect.new name
103+
end
104+
105+
@@introspect_cache[name]
106+
end
107+
108+
end
109+
38110
class Operation < Object
39111
# the layout of the VipsOperation struct
40112
module OperationLayout
@@ -87,24 +159,6 @@ def get_flags
87159
Vips::vips_operation_get_flags self
88160
end
89161

90-
# not quick! try to call this infrequently
91-
def get_construct_args
92-
args = []
93-
94-
argument_map do |pspec, argument_class, _argument_instance|
95-
flags = argument_class[:flags]
96-
if (flags & ARGUMENT_CONSTRUCT) != 0
97-
# names can include - as punctuation, but we always use _ in
98-
# Ruby
99-
name = pspec[:name].tr("-", "_")
100-
101-
args << [name, flags]
102-
end
103-
end
104-
105-
return args
106-
end
107-
108162
# search array for the first element to match a predicate ...
109163
# search inside subarrays and sub-hashes
110164
def self.find_inside object, &block
@@ -135,9 +189,7 @@ def self.imageize match_image, value
135189

136190
# set an operation argument, expanding constants and copying images as
137191
# required
138-
def set name, value, match_image = nil, flags = 0
139-
gtype = get_typeof name
140-
192+
def set name, value, match_image, flags, gtype
141193
if gtype == IMAGE_TYPE
142194
value = Operation::imageize match_image, value
143195

@@ -227,39 +279,10 @@ def self.call name, supplied, optional = {}, option_string = ""
227279
"optional = #{optional}, option_string = #{option_string}"
228280
}
229281

230-
op = Operation.new name
231-
232-
# find and classify all the arguments the operator can take
233-
args = op.get_construct_args
234-
required_input = []
235-
optional_input = {}
236-
required_output = []
237-
optional_output = {}
238-
args.each do |arg_name, flags|
239-
next if (flags & ARGUMENT_DEPRECATED) != 0
282+
introspect = Introspect.get name
240283

241-
if (flags & ARGUMENT_INPUT) != 0
242-
if (flags & ARGUMENT_REQUIRED) != 0
243-
required_input << [arg_name, flags]
244-
else
245-
optional_input[arg_name] = flags
246-
end
247-
end
248-
249-
# MODIFY INPUT args count as OUTPUT as well
250-
if (flags & ARGUMENT_OUTPUT) != 0 ||
251-
((flags & ARGUMENT_INPUT) != 0 &&
252-
(flags & ARGUMENT_MODIFY) != 0)
253-
if (flags & ARGUMENT_REQUIRED) != 0
254-
required_output << [arg_name, flags]
255-
else
256-
optional_output[arg_name] = flags
257-
end
258-
end
259-
end
284+
op = Operation.new name
260285

261-
# so we should have been supplied with n_required_input values, or
262-
# n_required_input + 1 if there's a hash of options at the end
263286
unless supplied.is_a? Array
264287
raise Vips::Error, "unable to call #{name}: " +
265288
"argument array is not an array"
@@ -268,19 +291,21 @@ def self.call name, supplied, optional = {}, option_string = ""
268291
raise Vips::Error, "unable to call #{name}: " +
269292
"optional arguments are not a hash"
270293
end
271-
if supplied.length != required_input.length
294+
295+
if supplied.length != introspect.required_input.length
272296
raise Vips::Error, "unable to call #{name}: " +
273297
"you supplied #{supplied.length} arguments, " +
274-
"but operation needs #{required_input.length}."
298+
"but operation needs " +
299+
"#{introspect.required_input.length}."
275300
end
276301

277-
# very that all supplied_optional keys are in optional_input or
302+
# all supplied_optional keys should be in optional_input or
278303
# optional_output
279304
optional.each do |key, _value|
280305
arg_name = key.to_s
281306

282-
unless optional_input.has_key?(arg_name) ||
283-
optional_output.has_key?(arg_name)
307+
unless introspect.optional_input.has_key?(arg_name) ||
308+
introspect.optional_output.has_key?(arg_name)
284309
raise Vips::Error, "unable to call #{name}: " +
285310
"unknown option #{arg_name}"
286311
end
@@ -303,12 +328,11 @@ def self.call name, supplied, optional = {}, option_string = ""
303328
end
304329

305330
# set all required inputs
306-
required_input.each_index do |i|
307-
arg_name = required_input[i][0]
308-
flags = required_input[i][1]
331+
introspect.required_input.each_index do |i|
332+
arg_name, flags, gtype, blurb = introspect.required_input[i]
309333
value = supplied[i]
310334

311-
op.set arg_name, value, match_image, flags
335+
op.set arg_name, value, match_image, flags, gtype
312336
end
313337

314338
# set all optional inputs
@@ -317,18 +341,19 @@ def self.call name, supplied, optional = {}, option_string = ""
317341

318342
arg_name = key.to_s
319343

320-
if optional_input.has_key? arg_name
321-
flags = optional_input[arg_name]
344+
if introspect.optional_input.has_key? arg_name
345+
_, flags, gtype, blurb = introspect.optional_input[arg_name]
322346

323-
op.set arg_name, value, match_image, flags
347+
op.set arg_name, value, match_image, flags, gtype
324348
end
325349
end
326350

327351
op = op.build
328352

329353
# get all required results
330354
result = []
331-
required_output.each do |arg_name, _flags|
355+
introspect.required_output.each do |details|
356+
arg_name, flags, gtype, blurb = details
332357
result << op.get(arg_name)
333358
end
334359

@@ -337,7 +362,7 @@ def self.call name, supplied, optional = {}, option_string = ""
337362
optional.each do |key, _value|
338363
arg_name = key.to_s
339364

340-
if optional_output.has_key? arg_name
365+
if introspect.optional_output.has_key? arg_name
341366
optional_results[arg_name] = op.get arg_name
342367
end
343368
end

0 commit comments

Comments
 (0)