Skip to content

Commit 0dee779

Browse files
committed
Optimize JSON::Pure::Generator by 2x-4x for simple options cases
* ruby --yjit benchmarks/bench.rb dump pure ruby 3.3.1 (2024-04-23 revision c56cd86388) +YJIT [x86_64-linux] Before: JSON.dump(obj) 604.604 (± 0.3%) i/s (1.65 ms/i) - 3.060k in 5.061200s After: JSON.dump(obj) 2.531k (± 0.4%) i/s (395.14 μs/i) - 12.801k in 5.058326s * ruby benchmarks/bench.rb dump pure truffleruby 24.1.0-dev-a8ebb51b, like ruby 3.2.2, Oracle GraalVM JVM [x86_64-linux] Before: JSON.dump(obj) 3.728k (± 9.4%) i/s (268.26 μs/i) - 18.559k in 5.068915s After: JSON.dump(obj) 7.835k (± 8.5%) i/s (127.63 μs/i) - 39.004k in 5.031116s
1 parent 5015a37 commit 0dee779

File tree

1 file changed

+61
-1
lines changed

1 file changed

+61
-1
lines changed

lib/json/pure/generator.rb

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ def configure(opts)
237237
opts.each do |key, value|
238238
instance_variable_set "@#{key}", value
239239
end
240+
241+
# NOTE: If adding new instance variables here, check whether #generate should check them for #generate_json
240242
@indent = opts[:indent] if opts.key?(:indent)
241243
@space = opts[:space] if opts.key?(:space)
242244
@space_before = opts[:space_before] if opts.key?(:space_before)
@@ -286,12 +288,70 @@ def to_h
286288
# created this method raises a
287289
# GeneratorError exception.
288290
def generate(obj)
289-
result = obj.to_json(self)
291+
if @indent.empty? and @space.empty? and @space_before.empty? and @object_nl.empty? and @array_nl.empty? and
292+
!@ascii_only and !@script_safe and @max_nesting == 0 and !@strict
293+
result = generate_json(obj, '')
294+
else
295+
result = obj.to_json(self)
296+
end
290297
JSON.valid_utf8?(result) or raise GeneratorError,
291298
"source sequence #{result.inspect} is illegal/malformed utf-8"
292299
result
293300
end
294301

302+
# Handles @allow_nan, @buffer_initial_length, other ivars must be the default value (see above)
303+
private def generate_json(obj, buf)
304+
case obj
305+
when Hash
306+
buf << '{'.freeze
307+
first = true
308+
obj.each_pair do |k,v|
309+
buf << ','.freeze unless first
310+
fast_serialize_string(k.to_s, buf)
311+
buf << ':'.freeze
312+
generate_json(v, buf)
313+
first = false
314+
end
315+
buf << '}'.freeze
316+
when Array
317+
buf << '['.freeze
318+
first = true
319+
obj.each do |e|
320+
buf << ','.freeze unless first
321+
generate_json(e, buf)
322+
first = false
323+
end
324+
buf << ']'.freeze
325+
when String
326+
fast_serialize_string(obj, buf)
327+
when Integer
328+
buf << obj.to_s
329+
else
330+
# Note: Float is handled this way since it is Float#to_s is slow anyway
331+
buf << obj.to_json(self)
332+
end
333+
end
334+
335+
# Assumes !@ascii_only, !@script_safe
336+
if Regexp.method_defined?(:match)
337+
private def fast_serialize_string(string, buf) # :nodoc:
338+
buf << '"'.freeze
339+
string = string.encode(::Encoding::UTF_8) unless string.encoding == ::Encoding::UTF_8
340+
341+
if /["\\\x0-\x1f]/n.match?(string)
342+
buf << string.gsub(/["\\\x0-\x1f]/n, MAP)
343+
else
344+
buf << string
345+
end
346+
buf << '"'.freeze
347+
end
348+
else
349+
# Ruby 2.3 compatibility
350+
private def fast_serialize_string(string, buf) # :nodoc:
351+
buf << string.to_json(self)
352+
end
353+
end
354+
295355
# Return the value returned by method +name+.
296356
def [](name)
297357
if respond_to?(name)

0 commit comments

Comments
 (0)