Skip to content

Commit

Permalink
Optimize JSON::Pure::Generator by 2x-4x for simple options cases
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
eregon committed May 9, 2024
1 parent 5015a37 commit 4e87543
Showing 1 changed file with 61 additions and 1 deletion.
62 changes: 61 additions & 1 deletion lib/json/pure/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,8 @@ def configure(opts)
opts.each do |key, value|
instance_variable_set "@#{key}", value
end

# NOTE: If adding new instance variables here, check whether #generate should check them for #generate_json
@indent = opts[:indent] if opts.key?(:indent)
@space = opts[:space] if opts.key?(:space)
@space_before = opts[:space_before] if opts.key?(:space_before)
Expand Down Expand Up @@ -286,12 +288,70 @@ def to_h
# created this method raises a
# GeneratorError exception.
def generate(obj)
result = obj.to_json(self)
if @indent.empty? and @space.empty? and @space_before.empty? and @object_nl.empty? and @array_nl.empty? and
!@ascii_only and !@script_safe and @max_nesting == 0 and !@strict
result = generate_json(obj, '')
else
result = obj.to_json(self)
end
JSON.valid_utf8?(result) or raise GeneratorError,
"source sequence #{result.inspect} is illegal/malformed utf-8"
result
end

# Handles @allow_nan, @buffer_initial_length, other ivars must be the default value (see above)
private def generate_json(obj, buf)
case obj
when Hash
buf << '{'.freeze
first = true
obj.each_pair do |k,v|
buf << ','.freeze unless first
fast_serialize_string(k.to_s, buf)
buf << ':'.freeze
generate_json(v, buf)
first = false
end
buf << '}'.freeze
when Array
buf << '['.freeze
first = true
obj.each do |e|
buf << ','.freeze unless first
generate_json(e, buf)
first = false
end
buf << ']'.freeze
when String
fast_serialize_string(obj, buf)
when Integer
buf << obj.to_s
else
# Note: Float is handled this way since it is Float#to_s is slow anyway
buf << obj.to_json(self)
end
end

# Assumes !@ascii_only, !@script_safe
if Regexp.method_defined?(:match?)
private def fast_serialize_string(string, buf) # :nodoc:
buf << '"'.freeze
string = string.encode(::Encoding::UTF_8) unless string.encoding == ::Encoding::UTF_8

if /["\\\x0-\x1f]/n.match?(string)
buf << string.gsub(/["\\\x0-\x1f]/n, MAP)
else
buf << string
end
buf << '"'.freeze
end
else
# Ruby 2.3 compatibility
private def fast_serialize_string(string, buf) # :nodoc:
buf << string.to_json(self)
end
end

# Return the value returned by method +name+.
def [](name)
if respond_to?(name)
Expand Down

0 comments on commit 4e87543

Please sign in to comment.