Skip to content

Commit 3d1316e

Browse files
authored
Optimize integer value dumps (#879)
To convert an Integer to a String, it needs to do division and remainder calculations many times. These calculations can consume more CPU cycles than other instructions. This patch will prepare a lookup table which has the remainder (0~99) of a number divided by 100 and use the table to omit the calculation. − | before | after | result -- | -- | -- | -- Oj.dump | 3.816M | 4.255M | 1.115x ### Environment - Linux - Manjaro Linux x86_64 - Kernel: 6.3.0-1-MANJARO - AMD Ryzen 7 5800H - gcc version 12.2.1 - Ruby 3.2.2 ### Before ``` Warming up -------------------------------------- Oj.dump 386.110k i/100ms Calculating ------------------------------------- Oj.dump 3.816M (± 1.4%) i/s - 19.306M in 5.060327s ``` ### After ``` Warming up -------------------------------------- Oj.dump 425.525k i/100ms Calculating ------------------------------------- Oj.dump 4.255M (± 0.2%) i/s - 21.276M in 5.000424s ``` ### Test code ```ruby require 'bundler/inline' gemfile do source 'https://rubygems.org' gem 'benchmark-ips' gem 'oj' end data = [646086033, 414841692, 706653378, 1069473884, 181209966, 515120040, 892957102, 689595306, 719771732, 651396679, 549722480] Benchmark.ips do |x| x.report('Oj.dump') { Oj.dump(data) } end ```
1 parent 41cf6b1 commit 3d1316e

2 files changed

Lines changed: 30 additions & 2 deletions

File tree

ext/oj/dump.c

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -995,6 +995,18 @@ void oj_dump_false(VALUE obj, int depth, Out out, bool as_ok) {
995995
*out->cur = '\0';
996996
}
997997

998+
static const char digits_table[] = "\
999+
00010203040506070809\
1000+
10111213141516171819\
1001+
20212223242526272829\
1002+
30313233343536373839\
1003+
40414243444546474849\
1004+
50515253545556575859\
1005+
60616263646566676869\
1006+
70717273747576777879\
1007+
80818283848586878889\
1008+
90919293949596979899";
1009+
9981010
void oj_dump_fixnum(VALUE obj, int depth, Out out, bool as_ok) {
9991011
char buf[32];
10001012
char *b = buf + sizeof(buf) - 1;
@@ -1017,9 +1029,19 @@ void oj_dump_fixnum(VALUE obj, int depth, Out out, bool as_ok) {
10171029
*b-- = '"';
10181030
}
10191031
if (0 < num) {
1020-
for (; 0 < num; num /= 10, b--) {
1021-
*b = (num % 10) + '0';
1032+
while (100 <= num) {
1033+
unsigned idx = num % 100 * 2;
1034+
*b-- = digits_table[idx + 1];
1035+
*b-- = digits_table[idx];
1036+
num /= 100;
1037+
}
1038+
if (num < 10) {
1039+
*b-- = num + '0';
1040+
} else {
1041+
*b-- = digits_table[num * 2 + 1];
1042+
*b-- = digits_table[num * 2];
10221043
}
1044+
10231045
if (neg) {
10241046
*b = '-';
10251047
} else {

test/test_compat.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,12 @@ def test_fixnum
111111
dump_and_load(1, false)
112112
end
113113

114+
def test_fixnum_array
115+
data = (1..1000).to_a
116+
json = Oj.dump(data, mode: :compat)
117+
assert_equal("[#{data.join(',')}]", json)
118+
end
119+
114120
def test_float
115121
dump_and_load(0.0, false)
116122
dump_and_load(0.56, false)

0 commit comments

Comments
 (0)