Skip to content

Commit

Permalink
Optimize integer value dumps (#879)
Browse files Browse the repository at this point in the history
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
```
  • Loading branch information
Watson1978 authored May 28, 2023
1 parent 41cf6b1 commit 3d1316e
Show file tree
Hide file tree
Showing 2 changed files with 30 additions and 2 deletions.
26 changes: 24 additions & 2 deletions ext/oj/dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -995,6 +995,18 @@ void oj_dump_false(VALUE obj, int depth, Out out, bool as_ok) {
*out->cur = '\0';
}

static const char digits_table[] = "\
00010203040506070809\
10111213141516171819\
20212223242526272829\
30313233343536373839\
40414243444546474849\
50515253545556575859\
60616263646566676869\
70717273747576777879\
80818283848586878889\
90919293949596979899";

void oj_dump_fixnum(VALUE obj, int depth, Out out, bool as_ok) {
char buf[32];
char *b = buf + sizeof(buf) - 1;
Expand All @@ -1017,9 +1029,19 @@ void oj_dump_fixnum(VALUE obj, int depth, Out out, bool as_ok) {
*b-- = '"';
}
if (0 < num) {
for (; 0 < num; num /= 10, b--) {
*b = (num % 10) + '0';
while (100 <= num) {
unsigned idx = num % 100 * 2;
*b-- = digits_table[idx + 1];
*b-- = digits_table[idx];
num /= 100;
}
if (num < 10) {
*b-- = num + '0';
} else {
*b-- = digits_table[num * 2 + 1];
*b-- = digits_table[num * 2];
}

if (neg) {
*b = '-';
} else {
Expand Down
6 changes: 6 additions & 0 deletions test/test_compat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ def test_fixnum
dump_and_load(1, false)
end

def test_fixnum_array
data = (1..1000).to_a
json = Oj.dump(data, mode: :compat)
assert_equal("[#{data.join(',')}]", json)
end

def test_float
dump_and_load(0.0, false)
dump_and_load(0.56, false)
Expand Down

0 comments on commit 3d1316e

Please sign in to comment.