mirror of
https://github.com/ruby/ruby.git
synced 2025-08-23 13:04:13 +02:00

Fix: https://github.com/ruby/json/issues/655 For very small documents, the biggest performance gap with alternatives is that the API impose that we allocate the `State` object. In a real world app this doesn't make much of a difference, but when running in a micro-benchmark this doubles the allocations, causing twice the amount of GC runs, making us look bad. However, unless we have to call a `to_json` method, the `State` object isn't visible, so with some refactoring, we can elude that allocation entirely. Instead we allocate the State internal struct on the stack, and if we need to call a `to_json` method, we allocate the `State` and spill the struct on the heap. As a result, `JSON.generate` is now as fast as re-using a `State` instance, as long as only primitives are generated. Before: ``` == Encoding small mixed (34 bytes) ruby 3.3.4 (2024-07-09 revisionbe1089c8ec
) +YJIT [arm64-darwin23] Warming up -------------------------------------- json (reuse) 598.654k i/100ms json 400.542k i/100ms oj 533.353k i/100ms Calculating ------------------------------------- json (reuse) 6.371M (± 8.6%) i/s (156.96 ns/i) - 31.729M in 5.059195s json 4.120M (± 6.6%) i/s (242.72 ns/i) - 20.828M in 5.090549s oj 5.622M (± 6.4%) i/s (177.86 ns/i) - 28.268M in 5.061473s Comparison: json (reuse): 6371126.6 i/s oj: 5622452.0 i/s - same-ish: difference falls within error json: 4119991.1 i/s - 1.55x slower == Encoding small nested array (121 bytes) ruby 3.3.4 (2024-07-09 revisionbe1089c8ec
) +YJIT [arm64-darwin23] Warming up -------------------------------------- json (reuse) 248.125k i/100ms json 215.255k i/100ms oj 217.531k i/100ms Calculating ------------------------------------- json (reuse) 2.628M (± 6.1%) i/s (380.55 ns/i) - 13.151M in 5.030281s json 2.185M (± 6.7%) i/s (457.74 ns/i) - 10.978M in 5.057655s oj 2.217M (± 6.7%) i/s (451.10 ns/i) - 11.094M in 5.044844s Comparison: json (reuse): 2627799.4 i/s oj: 2216824.8 i/s - 1.19x slower json: 2184669.5 i/s - 1.20x slower == Encoding small hash (65 bytes) ruby 3.3.4 (2024-07-09 revisionbe1089c8ec
) +YJIT [arm64-darwin23] Warming up -------------------------------------- json (reuse) 641.334k i/100ms json 322.745k i/100ms oj 642.450k i/100ms Calculating ------------------------------------- json (reuse) 7.133M (± 6.5%) i/s (140.19 ns/i) - 35.915M in 5.068201s json 4.615M (± 7.0%) i/s (216.70 ns/i) - 22.915M in 5.003718s oj 6.912M (± 6.4%) i/s (144.68 ns/i) - 34.692M in 5.047690s Comparison: json (reuse): 7133123.3 i/s oj: 6911977.1 i/s - same-ish: difference falls within error json: 4614696.6 i/s - 1.55x slower ``` After: ``` == Encoding small mixed (34 bytes) ruby 3.3.4 (2024-07-09 revisionbe1089c8ec
) +YJIT [arm64-darwin23] Warming up -------------------------------------- json (reuse) 572.751k i/100ms json 457.741k i/100ms oj 512.247k i/100ms Calculating ------------------------------------- json (reuse) 6.324M (± 6.9%) i/s (158.12 ns/i) - 31.501M in 5.023093s json 6.263M (± 6.9%) i/s (159.66 ns/i) - 31.126M in 5.017086s oj 5.569M (± 6.6%) i/s (179.56 ns/i) - 27.661M in 5.003739s Comparison: json (reuse): 6324183.5 i/s json: 6263204.9 i/s - same-ish: difference falls within error oj: 5569049.2 i/s - same-ish: difference falls within error == Encoding small nested array (121 bytes) ruby 3.3.4 (2024-07-09 revisionbe1089c8ec
) +YJIT [arm64-darwin23] Warming up -------------------------------------- json (reuse) 258.505k i/100ms json 242.335k i/100ms oj 220.678k i/100ms Calculating ------------------------------------- json (reuse) 2.589M (± 9.6%) i/s (386.17 ns/i) - 12.925M in 5.071853s json 2.594M (± 6.6%) i/s (385.46 ns/i) - 13.086M in 5.083035s oj 2.250M (± 2.3%) i/s (444.43 ns/i) - 11.255M in 5.004707s Comparison: json (reuse): 2589499.6 i/s json: 2594321.0 i/s - same-ish: difference falls within error oj: 2250064.0 i/s - 1.15x slower == Encoding small hash (65 bytes) ruby 3.3.4 (2024-07-09 revisionbe1089c8ec
) +YJIT [arm64-darwin23] Warming up -------------------------------------- json (reuse) 656.373k i/100ms json 644.135k i/100ms oj 650.283k i/100ms Calculating ------------------------------------- json (reuse) 7.202M (± 7.1%) i/s (138.84 ns/i) - 36.101M in 5.051438s json 7.278M (± 1.7%) i/s (137.40 ns/i) - 36.716M in 5.046300s oj 7.036M (± 1.7%) i/s (142.12 ns/i) - 35.766M in 5.084729s Comparison: json (reuse): 7202447.9 i/s json: 7277883.0 i/s - same-ish: difference falls within error oj: 7036115.2 i/s - same-ish: difference falls within error ```
91 lines
3.1 KiB
C++
91 lines
3.1 KiB
C++
#ifndef _GENERATOR_H_
|
|
#define _GENERATOR_H_
|
|
|
|
#include <math.h>
|
|
#include <ctype.h>
|
|
|
|
#include "ruby.h"
|
|
|
|
/* This is the fallback definition from Ruby 3.4 */
|
|
#ifndef RBIMPL_STDBOOL_H
|
|
#if defined(__cplusplus)
|
|
# if defined(HAVE_STDBOOL_H) && (__cplusplus >= 201103L)
|
|
# include <cstdbool>
|
|
# endif
|
|
#elif defined(HAVE_STDBOOL_H)
|
|
# include <stdbool.h>
|
|
#elif !defined(HAVE__BOOL)
|
|
typedef unsigned char _Bool;
|
|
# define bool _Bool
|
|
# define true ((_Bool)+1)
|
|
# define false ((_Bool)+0)
|
|
# define __bool_true_false_are_defined
|
|
#endif
|
|
#endif
|
|
|
|
/* ruby api and some helpers */
|
|
|
|
typedef struct JSON_Generator_StateStruct {
|
|
VALUE indent;
|
|
VALUE space;
|
|
VALUE space_before;
|
|
VALUE object_nl;
|
|
VALUE array_nl;
|
|
|
|
long max_nesting;
|
|
long depth;
|
|
long buffer_initial_length;
|
|
|
|
bool allow_nan;
|
|
bool ascii_only;
|
|
bool script_safe;
|
|
bool strict;
|
|
} JSON_Generator_State;
|
|
|
|
static VALUE mHash_to_json(int argc, VALUE *argv, VALUE self);
|
|
static VALUE mArray_to_json(int argc, VALUE *argv, VALUE self);
|
|
#ifdef RUBY_INTEGER_UNIFICATION
|
|
static VALUE mInteger_to_json(int argc, VALUE *argv, VALUE self);
|
|
#else
|
|
static VALUE mFixnum_to_json(int argc, VALUE *argv, VALUE self);
|
|
static VALUE mBignum_to_json(int argc, VALUE *argv, VALUE self);
|
|
#endif
|
|
static VALUE mFloat_to_json(int argc, VALUE *argv, VALUE self);
|
|
static VALUE mString_included_s(VALUE self, VALUE modul);
|
|
static VALUE mString_to_json(int argc, VALUE *argv, VALUE self);
|
|
static VALUE mString_to_json_raw_object(VALUE self);
|
|
static VALUE mString_to_json_raw(int argc, VALUE *argv, VALUE self);
|
|
static VALUE mString_Extend_json_create(VALUE self, VALUE o);
|
|
static VALUE mTrueClass_to_json(int argc, VALUE *argv, VALUE self);
|
|
static VALUE mFalseClass_to_json(int argc, VALUE *argv, VALUE self);
|
|
static VALUE mNilClass_to_json(int argc, VALUE *argv, VALUE self);
|
|
static VALUE mObject_to_json(int argc, VALUE *argv, VALUE self);
|
|
static void State_free(void *state);
|
|
static VALUE cState_s_allocate(VALUE klass);
|
|
|
|
static VALUE cState_generate(VALUE self, VALUE obj);
|
|
static VALUE cState_from_state_s(VALUE self, VALUE opts);
|
|
static VALUE cState_indent(VALUE self);
|
|
static VALUE cState_indent_set(VALUE self, VALUE indent);
|
|
static VALUE cState_space(VALUE self);
|
|
static VALUE cState_space_set(VALUE self, VALUE space);
|
|
static VALUE cState_space_before(VALUE self);
|
|
static VALUE cState_space_before_set(VALUE self, VALUE space_before);
|
|
static VALUE cState_object_nl(VALUE self);
|
|
static VALUE cState_object_nl_set(VALUE self, VALUE object_nl);
|
|
static VALUE cState_array_nl(VALUE self);
|
|
static VALUE cState_array_nl_set(VALUE self, VALUE array_nl);
|
|
static VALUE cState_max_nesting(VALUE self);
|
|
static VALUE cState_max_nesting_set(VALUE self, VALUE depth);
|
|
static VALUE cState_allow_nan_p(VALUE self);
|
|
static VALUE cState_ascii_only_p(VALUE self);
|
|
static VALUE cState_depth(VALUE self);
|
|
static VALUE cState_depth_set(VALUE self, VALUE depth);
|
|
static VALUE cState_script_safe(VALUE self);
|
|
static VALUE cState_script_safe_set(VALUE self, VALUE depth);
|
|
static VALUE cState_strict(VALUE self);
|
|
static VALUE cState_strict_set(VALUE self, VALUE strict);
|
|
|
|
static const rb_data_type_t JSON_Generator_State_type;
|
|
|
|
#endif
|