From f3640e00d1b7838b5055b433c7494feb463c7c66 Mon Sep 17 00:00:00 2001 From: Watson Date: Sun, 25 Jul 2021 18:21:08 +0900 Subject: [PATCH] Improve `Oj.dump` performance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch uses standard C library to copy the string because copying one byte at a time is slow. This patch will improve `Oj.dump` performance as following. - | before | after | result -- | -- | -- | -- Oj.dump | 689.236k | 1.853M | 2.69x Oj.dump (compat) | 476.107k | 827.446k | 1.74x Oj.dump (rails) | 464.545k | 644.494k | 1.39x ### Environment - MacBook Air (M1, 2020) - macOS 12.0 beta 3 - Apple M1 - Ruby 3.0.2 ### Before ``` Warming up -------------------------------------- Oj.dump 69.210k i/100ms Oj.dump (compat) 47.123k i/100ms Oj.dump (rails) 45.911k i/100ms Calculating ------------------------------------- Oj.dump 689.236k (± 0.2%) i/s - 3.460M in 5.020801s Oj.dump (compat) 476.107k (± 0.9%) i/s - 2.403M in 5.048128s Oj.dump (rails) 464.545k (± 0.9%) i/s - 2.341M in 5.040711s ``` ### After ``` Warming up -------------------------------------- Oj.dump 187.096k i/100ms Oj.dump (compat) 82.879k i/100ms Oj.dump (rails) 64.371k i/100ms Calculating ------------------------------------- Oj.dump 1.853M (± 0.3%) i/s - 9.355M in 5.049406s Oj.dump (compat) 827.446k (± 0.2%) i/s - 4.144M in 5.008145s Oj.dump (rails) 644.494k (± 0.2%) i/s - 3.283M in 5.093814s ``` ### Test code ```ruby require 'benchmark/ips' require 'oj' data = { 'short_string': 'a' * 50, 'long_string': 'b' * 255, 'utf8_string': 'あいうえお' * 10 } Benchmark.ips do |x| x.report('Oj.dump') { Oj.dump(data) } x.report('Oj.dump (compat)') { Oj.dump(data, mode: :compat) } x.report('Oj.dump (rails)') { Oj.dump(data, mode: :rails) } end ``` --- ext/oj/dump.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/ext/oj/dump.c b/ext/oj/dump.c index ded508df..7fa95a2a 100644 --- a/ext/oj/dump.c +++ b/ext/oj/dump.c @@ -535,7 +535,7 @@ void oj_dump_xml_time(VALUE obj, Out out) { } if ((0 == nsec && !out->opts->sec_prec_set) || 0 == out->opts->sec_prec) { if (0 == tzsecs && rb_funcall2(obj, oj_utcq_id, 0, 0)) { - sprintf(buf, + int len = sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02dZ", ti.year, ti.mon, @@ -543,9 +543,9 @@ void oj_dump_xml_time(VALUE obj, Out out) { ti.hour, ti.min, ti.sec); - oj_dump_cstr(buf, 20, 0, 0, out); + oj_dump_cstr(buf, len, 0, 0, out); } else { - sprintf(buf, + int len = sprintf(buf, "%04d-%02d-%02dT%02d:%02d:%02d%c%02d:%02d", ti.year, ti.mon, @@ -556,27 +556,25 @@ void oj_dump_xml_time(VALUE obj, Out out) { tzsign, tzhour, tzmin); - oj_dump_cstr(buf, 25, 0, 0, out); + oj_dump_cstr(buf, len, 0, 0, out); } } else if (0 == tzsecs && rb_funcall2(obj, oj_utcq_id, 0, 0)) { char format[64] = "%04d-%02d-%02dT%02d:%02d:%02d.%09ldZ"; - int len = 30; + int len; if (9 > out->opts->sec_prec) { format[32] = '0' + out->opts->sec_prec; - len -= 9 - out->opts->sec_prec; } - sprintf(buf, format, ti.year, ti.mon, ti.day, ti.hour, ti.min, ti.sec, (long)nsec); + len = sprintf(buf, format, ti.year, ti.mon, ti.day, ti.hour, ti.min, ti.sec, (long)nsec); oj_dump_cstr(buf, len, 0, 0, out); } else { char format[64] = "%04d-%02d-%02dT%02d:%02d:%02d.%09ld%c%02d:%02d"; - int len = 35; + int len; if (9 > out->opts->sec_prec) { format[32] = '0' + out->opts->sec_prec; - len -= 9 - out->opts->sec_prec; } - sprintf(buf, + len = sprintf(buf, format, ti.year, ti.mon, @@ -827,9 +825,8 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou if (is_sym) { *out->cur++ = ':'; } - for (; '\0' != *str; str++) { - *out->cur++ = *str; - } + strncpy(out->cur, str, cnt); + out->cur += cnt; *out->cur++ = '"'; } else { const char *end = str + cnt;