x.json2: predefine buffer capacity for encoding to avoid reallocations (#20920)

This commit is contained in:
Hitalo Souza 2024-03-14 15:23:07 -04:00 committed by GitHub
parent 9a0435db05
commit 49b7f9a94a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 204 additions and 1 deletions

View File

@ -7,6 +7,8 @@ import benchmark
// recommendations:
// ./v wipe-cache && MAX_ITERATIONS=100_000 ./v run vlib/v/tests/bench/bench_json_vs_json2.v
// ./v wipe-cache && MAX_ITERATIONS=100_000 ./v -prod crun vlib/v/tests/bench/bench_json_vs_json2.v
// ./v wipe-cache && ./v -gc boehm_leak -o testcase_leak -prod vlib/v/tests/bench/bench_json_vs_json2.v && ./testcase_leak 2>leaks.txt
// ./v wipe-cache && ./v -prod vlib/v/tests/bench/bench_json_vs_json2.v -o b_out && valgrind --track-origins=yes ./b_out
const max_iterations = os.getenv_opt('MAX_ITERATIONS') or { '1000' }.int()

98
vlib/x/json2/count.v Normal file
View File

@ -0,0 +1,98 @@
module json2
import time
struct Count {
mut:
total int
}
// get_total
fn (mut count Count) get_total() int {
return count.total
}
// reset_total
fn (mut count Count) reset_total() {
count.total = 0
}
// count_chars count json sizen whithout new encode
fn (mut count Count) count_chars[T](val T) {
$if val is $option {
workaround := val
if workaround != none {
count.count_chars(val)
}
} $else $if T is string {
count.chars_in_string(val)
} $else $if T is $sumtype {
$for v in val.variants {
if val is v {
count.count_chars(val)
}
}
} $else $if T is $alias {
// TODO
} $else $if T is time.Time {
count.total += 26 // "YYYY-MM-DDTHH:mm:ss.123Z"
} $else $if T is $map {
count.total++ // {
for k, v in val {
count.count_chars(k)
count.total++ // :
count.count_chars(v)
}
count.total++ // }
} $else $if T is $array {
count.total += 2 // []
if val.len > 0 {
for element in val {
count.count_chars(element)
}
count.total += val.len - 1 // ,
}
} $else $if T is $struct {
count.chars_in_struct(val)
} $else $if T is $enum {
count.count_chars(int(val))
} $else $if T is $int {
// TODO benchmark
mut abs_val := val
if val < 0 {
count.total++ // -
abs_val = -val
}
for number_value := abs_val; number_value >= 1; number_value /= 10 {
count.total++
}
if val == 0 {
count.total++
}
} $else $if T is $float {
// TODO
} $else $if T is bool {
if val {
count.total += 4 // true
} else {
count.total += 5 // false
}
} $else {
}
}
// chars_in_struct
fn (mut count Count) chars_in_struct[T](val T) {
count.total += 2 // {}
$for field in T.fields {
// TODO handle attributes
count.total += field.name.len + 3 // "":
workaround := val.$(field.name)
count.count_chars(workaround)
}
}
// chars_in_string
fn (mut count Count) chars_in_string(val string) {
count.total += val.len + 2 // ""
}

100
vlib/x/json2/count_test.v Normal file
View File

@ -0,0 +1,100 @@
module json2
import time
const fixed_time = time.Time{
year: 2022
month: 3
day: 11
hour: 13
minute: 54
second: 25
unix: 1647006865
}
type StringAlias = string
type BoolAlias = bool
type IntAlias = int
type TimeAlias = time.Time
type StructAlias = StructType[int]
type EnumAlias = Enumerates
type SumTypes = StructType[string] | []SumTypes | []string | bool | int | string | time.Time
enum Enumerates {
a
b
c
d
e = 99
f
}
struct StructType[T] {
mut:
val T
}
struct StructTypeOption[T] {
mut:
val ?T
}
struct StructTypePointer[T] {
mut:
val &T
}
fn count_test[T](value T) {
mut count := Count{0}
count.count_chars(value)
assert encode(value).len == count.get_total()
}
fn test_empty() {
count_test(map[string]string{})
count_test([]string{})
count_test(StructType[bool]{})
count_test(map[string]string{})
}
fn test_types() {
count_test(StructType[string]{})
count_test(StructType[string]{ val: '' })
count_test(StructType[string]{ val: 'abcd' })
count_test(StructType[bool]{ val: false })
count_test(StructType[bool]{ val: true })
count_test(StructType[int]{ val: 26 })
count_test(StructType[int]{ val: 1 })
count_test(StructType[int]{ val: -125 })
count_test(StructType[u64]{ val: u64(-1) })
count_test(StructType[time.Time]{})
count_test(StructType[time.Time]{ val: json2.fixed_time })
count_test(StructType[StructType[int]]{
val: StructType[int]{
val: 1
}
})
count_test(StructType[Enumerates]{})
count_test(StructType[Enumerates]{})
count_test(StructType[Enumerates]{ val: Enumerates.f })
count_test(StructType[[]int]{})
count_test(StructType[[]int]{ val: [0] })
count_test(StructType[[]int]{ val: [0, 1, 0, 2, 3, 2, 5, 1] })
}

View File

@ -53,7 +53,10 @@ pub fn encode[T](val T) string {
$if T is $array {
return encode_array(val)
} $else {
mut buf := []u8{}
mut count := Count{0}
count.count_chars(val)
mut buf := []u8{cap: count.total}
defer {
unsafe { buf.free() }