Skip to content

Commit 0b4a912

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 e2dd834 commit 0b4a912

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
@@ -239,6 +239,8 @@ def configure(opts)
239239
opts.each do |key, value|
240240
instance_variable_set "@#{key}", value
241241
end
242+
243+
# NOTE: If adding new instance variables here, check whether #generate should check them for #generate_json
242244
@indent = opts[:indent] if opts.key?(:indent)
243245
@space = opts[:space] if opts.key?(:space)
244246
@space_before = opts[:space_before] if opts.key?(:space_before)
@@ -288,12 +290,70 @@ def to_h
288290
# created this method raises a
289291
# GeneratorError exception.
290292
def generate(obj)
291-
result = obj.to_json(self)
293+
if @indent.empty? and @space.empty? and @space_before.empty? and @object_nl.empty? and @array_nl.empty? and
294+
!@ascii_only and !@script_safe and @max_nesting == 0 and !@strict
295+
result = generate_json(obj, '')
296+
else
297+
result = obj.to_json(self)
298+
end
292299
JSON.valid_utf8?(result) or raise GeneratorError,
293300
"source sequence #{result.inspect} is illegal/malformed utf-8"
294301
result
295302
end
296303

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

0 commit comments

Comments
 (0)