Skip to content

Commit

Permalink
x.json2: optimise encoding to be faster than cJSON with -prod (#20052)
Browse files Browse the repository at this point in the history
  • Loading branch information
enghitalo committed Dec 29, 2023
1 parent c4180d4 commit 7fc3159
Show file tree
Hide file tree
Showing 9 changed files with 310 additions and 203 deletions.
8 changes: 7 additions & 1 deletion cmd/tools/vls.v
Expand Up @@ -113,7 +113,13 @@ fn (upd VlsUpdater) update_manifest(new_path string, from_source bool, timestamp
manifest['last_updated'] = json2.Any(timestamp.format_ss())
manifest['from_source'] = json2.Any(from_source)

json_enc.encode_value(manifest, mut manifest_file)!
mut buffer := []u8{}

json_enc.encode_value(manifest, mut buffer)!

manifest_file.write(buffer)!

unsafe { buffer.free() }
}

fn (upd VlsUpdater) init_download_prebuilt() ! {
Expand Down
7 changes: 6 additions & 1 deletion vlib/v/gen/c/cgen.v
Expand Up @@ -5524,7 +5524,12 @@ fn (mut g Gen) const_decl_precomputed(mod string, name string, field_name string
return false
}
escval := util.smart_quote(u8(rune_code).ascii_str(), false)
g.const_decl_write_precomputed(mod, styp, cname, field_name, "'${escval}'")

g.global_const_defs[util.no_dots(field_name)] = GlobalConstDef{
mod: mod
def: "#define ${cname} '${escval}'"
order: -1
}
} else {
g.const_decl_write_precomputed(mod, styp, cname, field_name, u32(ct_value).str())
}
Expand Down
16 changes: 8 additions & 8 deletions vlib/v/tests/bench/bench_json_vs_json2.v
Expand Up @@ -5,8 +5,8 @@ import time
import benchmark

// recommendations:
// MAX_ITERATIONS=90_000 ./v run vlib/v/tests/bench/bench_json_vs_json2.v
// MAX_ITERATIONS=90_000 ./v -no-bounds-checking -prod -cc clang-15 crun vlib/v/tests/bench/bench_json_vs_json2.v
// MAX_ITERATIONS=100_000 ./v run vlib/v/tests/bench/bench_json_vs_json2.v
// MAX_ITERATIONS=100_000 ./v -no-bounds-checking -prod -cc clang-15 crun vlib/v/tests/bench/bench_json_vs_json2.v

const max_iterations = os.getenv_opt('MAX_ITERATIONS') or { '1000' }.int()

Expand Down Expand Up @@ -77,7 +77,7 @@ fn benchmark_measure_json_vs_json2_on_complex_struct() ! {
return error('json.decode ${p}')
}
}
b.measure('json.decode')
b.measure('json.decode\n')

measure_json_encode_old_vs_new(json.decode(Person, s)!)!
}
Expand Down Expand Up @@ -124,7 +124,7 @@ fn benchmark_measure_decode_by_type() ! {
return error('json.decode ${d}')
}
}
b.measure(' json.decode StructType[string]')
b.measure(' json.decode StructType[string]\n')

vb := '{"val": true}'
for _ in 0 .. max_iterations {
Expand All @@ -140,7 +140,7 @@ fn benchmark_measure_decode_by_type() ! {
return error('json.decode ${d}')
}
}
b.measure(' json.decode StructType[bool]')
b.measure(' json.decode StructType[bool]\n')

v0 := '{"val": 0}'
for _ in 0 .. max_iterations {
Expand All @@ -156,7 +156,7 @@ fn benchmark_measure_decode_by_type() ! {
return error('json.decode ${d}')
}
}
b.measure(' json.decode StructType[int]')
b.measure(' json.decode StructType[int]\n')

vt := '{"val": "2015-01-06 15:47:32"}'
for _ in 0 .. max_iterations {
Expand All @@ -172,7 +172,7 @@ fn benchmark_measure_decode_by_type() ! {
return error('json2.decode ${d}')
}
}
b.measure(' json.decode StructType[time.Time]')
b.measure(' json.decode StructType[time.Time]\n')
}

fn measure_json_encode_old_vs_new[T](val T) ! {
Expand All @@ -191,5 +191,5 @@ fn measure_json_encode_old_vs_new[T](val T) ! {
return error('json.encode ${e}')
}
}
b.measure(' json.encode ${typename}')
b.measure(' json.encode ${typename}\n')
}
15 changes: 15 additions & 0 deletions vlib/x/json2/README.md
Expand Up @@ -150,3 +150,18 @@ The following list shows the possible outputs when casting a value to an incompa
3. Casting non-string values to string (`str()`) will return the
JSON string representation of the value.
4. Casting non-numeric values to int/float (`int()`/`i64()`/`f32()`/`f64()`) will return zero.

## Encoding using string builder instead of []u8
To be more performant, `json2`, in PR 20052, decided to use buffers directly instead of Writers.
If you want to use Writers you can follow the steps below:

```v ignore
mut sb := strings.new_builder(64)
mut buffer := []u8{}
json2.encode_value(<some value to be encoded here>, mut buffer)!
sb.write(buffer)!
unsafe { buffer.free() }
```
6 changes: 3 additions & 3 deletions vlib/x/json2/encode_struct_test.v
Expand Up @@ -229,19 +229,19 @@ fn test_alias() {

fn test_pointer() {
mut string_initialized_with_reference := ''
assert json.encode(StructTypePointer[string]{ val: 0 }) == '{}'
assert json.encode(StructTypePointer[string]{ val: unsafe { nil } }) == '{}'
assert json.encode(StructTypePointer[string]{ val: &string_initialized_with_reference }) == '{"val":""}'
string_initialized_with_reference = 'a'
assert json.encode(StructTypePointer[string]{ val: &string_initialized_with_reference }) == '{"val":"a"}'

mut bool_initialized_with_reference := false
assert json.encode(StructTypePointer[bool]{ val: 0 }) == '{}'
assert json.encode(StructTypePointer[bool]{ val: unsafe { nil } }) == '{}'
assert json.encode(StructTypePointer[bool]{ val: &bool_initialized_with_reference }) == '{"val":false}'
bool_initialized_with_reference = true
assert json.encode(StructTypePointer[bool]{ val: &bool_initialized_with_reference }) == '{"val":true}'

mut int_initialized_with_reference := 0
assert json.encode(StructTypePointer[int]{ val: 0 }) == '{}'
assert json.encode(StructTypePointer[int]{ val: unsafe { nil } }) == '{}'
assert json.encode(StructTypePointer[int]{ val: &int_initialized_with_reference }) == '{"val":0}'
int_initialized_with_reference = 1
assert json.encode(StructTypePointer[int]{ val: &int_initialized_with_reference }) == '{"val":1}'
Expand Down

0 comments on commit 7fc3159

Please sign in to comment.