From e14d18dbd15ef8958c1eafcbf112df459516aa8e Mon Sep 17 00:00:00 2001 From: Shizuo Fujita Date: Sun, 29 Dec 2024 04:01:42 +0900 Subject: [PATCH] Sort the conditional statements (#946) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch will sort the conditional statements to reduce the conditional branches by skipping if statements that do not need to be executed depending on the value of `first`. − | before | after | result -- | -- | -- | -- Oj.load | 325.594k | 341.356k | 1.048x ### Environment - Linux - Manjaro Linux x86_64 - Kernel: 6.12.4-1-MANJARO - AMD Ryzen 9 8945HS - gcc version 14.2.1 - Ruby 3.4.1 ### Code ```ruby require 'bundler/inline' gemfile do source 'https://rubygems.org' gem 'benchmark-ips' gem 'oj' end json = '{"a":"Alpha","b":true,"c":12345,"d":[true,[false,[-123456789,null],3.9676,["Something else.",false],null]],"e":{"zero":null,"one":1,"two":2,"three":[3],"four":[0,1,2,3,4]},"f":null,"h":{"a":{"b":{"c":{"d":{"e":{"f":{"g":null}}}}}}},"i":[[[[[[[null]]]]]]]}' Benchmark.ips do |x| x.time = 10 x.report('Oj.load compat') { Oj.load(json, mode: :compat) } end ``` ### Before ``` $ ruby json_load.rb ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [x86_64-linux] Warming up -------------------------------------- Oj.load compat 32.352k i/100ms Calculating ------------------------------------- Oj.load compat 325.594k (± 1.8%) i/s (3.07 μs/i) - 3.268M in 10.039265s ``` ### After ``` $ ruby json_load.rb ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [x86_64-linux] Warming up -------------------------------------- Oj.load compat 34.274k i/100ms Calculating ------------------------------------- Oj.load compat 341.356k (± 3.6%) i/s (2.93 μs/i) - 3.427M in 10.056686s ``` --- ext/oj/parse.c | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/ext/oj/parse.c b/ext/oj/parse.c index 650e7006..9a80083c 100644 --- a/ext/oj/parse.c +++ b/ext/oj/parse.c @@ -681,7 +681,7 @@ void oj_parse2(ParseInfo pi) { pi->cur = pi->json; err_init(&pi->err); while (1) { - if (0 < pi->max_depth && pi->max_depth <= pi->stack.tail - pi->stack.head - 1) { + if (RB_UNLIKELY(0 < pi->max_depth && pi->max_depth <= pi->stack.tail - pi->stack.head - 1)) { VALUE err_clas = oj_get_json_err_class("NestingError"); oj_set_error_at(pi, err_clas, __FILE__, __LINE__, "Too deeply nested."); @@ -689,18 +689,20 @@ void oj_parse2(ParseInfo pi) { return; } next_non_white(pi); - if (!first && '\0' != *pi->cur) { - oj_set_error_at(pi, - oj_parse_error_class, - __FILE__, - __LINE__, - "unexpected characters after the JSON document"); - } - - // If no tokens are consumed (i.e. empty string), throw a parse error - // this is the behavior of JSON.parse in both Ruby and JS. - if (No == pi->options.empty_string && 1 == first && '\0' == *pi->cur) { - oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); + if (first) { + // If no tokens are consumed (i.e. empty string), throw a parse error + // this is the behavior of JSON.parse in both Ruby and JS. + if (RB_UNLIKELY('\0' == *pi->cur && No == pi->options.empty_string)) { + oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); + } + } else { + if (RB_UNLIKELY('\0' != *pi->cur)) { + oj_set_error_at(pi, + oj_parse_error_class, + __FILE__, + __LINE__, + "unexpected characters after the JSON document"); + } } switch (*pi->cur++) { @@ -761,7 +763,7 @@ void oj_parse2(ParseInfo pi) { case '\0': pi->cur--; return; default: oj_set_error_at(pi, oj_parse_error_class, __FILE__, __LINE__, "unexpected character"); return; } - if (err_has(&pi->err)) { + if (RB_UNLIKELY(err_has(&pi->err))) { return; } if (stack_empty(&pi->stack)) {