mirror of
https://github.com/protocolbuffers/protobuf
synced 2026-08-26 02:23:14 -04:00
The unit tests added in the initial implementation in cl/925012500 and cl/922729754 were fairly minimal. I took some inspiration from our C++ and Java unit tests for JSON's custom enum name parsing to come up with these new test cases. This change also adds a python Benchmark that we can use to verify that our optimizations in cl/952170251 actually works. Benchmark results across all three proto implementations (cpp, python, upb): === C++ Extension Protos (json_format_benchmark_cpp_protos) === [BENCHMARK] ParseJsonDefault: med 8.70 us/op | p99 9.56 us/op | mean 8.74 ± 0.15 us/op [BENCHMARK] ParseJsonCustom: med 17.39 us/op | p99 21.64 us/op | mean 17.43 ± 0.51 us/op [BENCHMARK] ParseJsonUnknownIgnored: med 26.97 us/op | p99 32.04 us/op | mean 27.06 ± 0.65 us/op [BENCHMARK] ParseRepeatedJsonDefault: med 1.71 us/item | p99 1.83 us/item | mean 1.71 ± 0.02 us/item [BENCHMARK] ParseRepeatedJsonCustom: med 9.18 us/item | p99 9.88 us/item | mean 9.20 ± 0.11 us/item [BENCHMARK] ParseRepeatedJsonUnknownIgnored: med 18.79 us/item | p99 19.19 us/item | mean 18.81 ± 0.13 us/item === UPB Extension Protos (json_format_benchmark_upb_protos) === [BENCHMARK] ParseJsonDefault: med 8.70 us/op | p99 9.56 us/op | mean 8.74 ± 0.15 us/op [BENCHMARK] ParseJsonCustom: med 17.39 us/op | p99 21.64 us/op | mean 17.43 ± 0.51 us/op [BENCHMARK] ParseJsonUnknownIgnored: med 26.97 us/op | p99 32.04 us/op | mean 27.06 ± 0.65 us/op [BENCHMARK] ParseRepeatedJsonDefault: med 1.71 us/item | p99 1.83 us/item | mean 1.71 ± 0.02 us/item [BENCHMARK] ParseRepeatedJsonCustom: med 9.18 us/item | p99 9.88 us/item | mean 9.20 ± 0.11 us/item [BENCHMARK] ParseRepeatedJsonUnknownIgnored: med 18.79 us/item | p99 19.19 us/item | mean 18.81 ± 0.13 us/item === Pure Python Protos (json_format_benchmark_python_protos) === [BENCHMARK] ParseJsonDefault: med 10.26 us/op | p99 11.20 us/op | mean 10.30 ± 0.25 us/op [BENCHMARK] ParseJsonCustom: med 16.59 us/op | p99 18.35 us/op | mean 16.70 ± 0.45 us/op [BENCHMARK] ParseJsonUnknownIgnored: med 25.09 us/op | p99 28.22 us/op | mean 25.27 ± 0.65 us/op [BENCHMARK] ParseRepeatedJsonDefault: med 1.94 us/item | p99 3.25 us/item | mean 1.97 ± 0.14 us/item [BENCHMARK] ParseRepeatedJsonCustom: med 7.16 us/item | p99 10.35 us/item | mean 7.24 ± 0.39 us/item [BENCHMARK] ParseRepeatedJsonUnknownIgnored: med 15.43 us/item | p99 19.54 us/item | mean 15.54 ± 0.49 us/item PiperOrigin-RevId: 966820541
250 lines
7.3 KiB
Python
250 lines
7.3 KiB
Python
# Protocol Buffers - Google's data interchange format
|
|
# Copyright 2026 Google LLC. All rights reserved.
|
|
#
|
|
# Use of this source code is governed by a BSD-style
|
|
# license that can be found in the LICENSE file or at
|
|
# https://developers.google.com/open-source/licenses/bsd
|
|
|
|
"""Microbenchmarks for JSON format parsing and custom enum names."""
|
|
|
|
import statistics
|
|
import timeit
|
|
import unittest
|
|
|
|
from google.protobuf import json_format
|
|
|
|
from google.protobuf.json import json_enumval_custom_string_pb2
|
|
|
|
|
|
class JsonFormatBenchmark(unittest.TestCase):
|
|
"""Microbenchmarks for JSON format parsing."""
|
|
|
|
def test_benchmark_parse_json(self):
|
|
"""Benchmarks parsing of default, custom, and unknown enum names in JSON."""
|
|
default_payloads = [
|
|
'{"armor":"ARMOR_GREAT_HELM"}',
|
|
'{"armor":"ARMOR_GAUNTLET"}',
|
|
'{"armor":"ARMOR_PLATE"}',
|
|
'{"armor":"ARMOR_COIF"}',
|
|
'{"armor":"ARMOR_PAULDRON"}',
|
|
'{"armor":"ARMOR_SABATON"}',
|
|
'{"armor":"ARMOR_HACHI_MAI_DO"}',
|
|
]
|
|
custom_payloads = [
|
|
'{"armor":"gr8 helm"}',
|
|
'{"armor":"a\\"b"}',
|
|
'{"armor":"\\"plate\\""}',
|
|
'{"armor":""}',
|
|
'{"armor":"p\\taul\\ndron"}',
|
|
'{"armor":"sabaton"}',
|
|
'{"armor":"8"}',
|
|
]
|
|
unknown_payloads = [
|
|
'{"armor":"UNKNOWN_1"}',
|
|
'{"armor":"UNKNOWN_2"}',
|
|
'{"armor":"UNKNOWN_3"}',
|
|
'{"armor":"UNKNOWN_4"}',
|
|
'{"armor":"UNKNOWN_5"}',
|
|
'{"armor":"UNKNOWN_6"}',
|
|
'{"armor":"UNKNOWN_7"}',
|
|
]
|
|
|
|
repeated_default_payload = (
|
|
'{"armors":['
|
|
+ ','.join(
|
|
[
|
|
'"ARMOR_GREAT_HELM"',
|
|
'"ARMOR_GAUNTLET"',
|
|
'"ARMOR_PLATE"',
|
|
'"ARMOR_COIF"',
|
|
'"ARMOR_PAULDRON"',
|
|
'"ARMOR_SABATON"',
|
|
'"ARMOR_HACHI_MAI_DO"',
|
|
]
|
|
* 14
|
|
)
|
|
+ ']}'
|
|
)
|
|
repeated_custom_payload = (
|
|
'{"armors":['
|
|
+ ','.join(
|
|
[
|
|
'"gr8 helm"',
|
|
'"a\\"b"',
|
|
'"\\"plate\\""',
|
|
'""',
|
|
'"p\\taul\\ndron"',
|
|
'"sabaton"',
|
|
'"8"',
|
|
]
|
|
* 14
|
|
)
|
|
+ ']}'
|
|
)
|
|
repeated_unknown_payload = (
|
|
'{"armors":['
|
|
+ ','.join(
|
|
[
|
|
'"UNKNOWN_1"',
|
|
'"UNKNOWN_2"',
|
|
'"UNKNOWN_3"',
|
|
'"UNKNOWN_4"',
|
|
'"UNKNOWN_5"',
|
|
'"UNKNOWN_6"',
|
|
'"UNKNOWN_7"',
|
|
]
|
|
* 14
|
|
)
|
|
+ ']}'
|
|
)
|
|
|
|
iterations = 10
|
|
trials = 100
|
|
num_parses = 10
|
|
num_items = 98
|
|
|
|
msg = json_enumval_custom_string_pb2.Knight()
|
|
|
|
# Warmup phase to eliminate initial compilation/import overhead
|
|
for _ in range(200):
|
|
for p in custom_payloads:
|
|
msg.Clear()
|
|
json_format.Parse(p, msg)
|
|
|
|
def run_def():
|
|
for p in default_payloads:
|
|
for _ in range(num_parses):
|
|
msg.Clear()
|
|
json_format.Parse(p, msg)
|
|
|
|
def run_cust():
|
|
for p in custom_payloads:
|
|
for _ in range(num_parses):
|
|
msg.Clear()
|
|
json_format.Parse(p, msg)
|
|
|
|
def run_unk():
|
|
for p in unknown_payloads:
|
|
for _ in range(num_parses):
|
|
msg.Clear()
|
|
json_format.Parse(p, msg, ignore_unknown_fields=True)
|
|
|
|
def run_rep_def():
|
|
for _ in range(num_parses):
|
|
msg.Clear()
|
|
json_format.Parse(repeated_default_payload, msg)
|
|
|
|
def run_rep_cust():
|
|
for _ in range(num_parses):
|
|
msg.Clear()
|
|
json_format.Parse(repeated_custom_payload, msg)
|
|
|
|
def run_rep_unk():
|
|
for _ in range(num_parses):
|
|
msg.Clear()
|
|
json_format.Parse(
|
|
repeated_unknown_payload, msg, ignore_unknown_fields=True
|
|
)
|
|
|
|
benchmarks = [
|
|
(
|
|
'run_def',
|
|
timeit.Timer(run_def, setup=msg.Clear),
|
|
len(default_payloads) * num_parses,
|
|
),
|
|
(
|
|
'run_cust',
|
|
timeit.Timer(run_cust, setup=msg.Clear),
|
|
len(custom_payloads) * num_parses,
|
|
),
|
|
(
|
|
'run_unk',
|
|
timeit.Timer(run_unk, setup=msg.Clear),
|
|
len(unknown_payloads) * num_parses,
|
|
),
|
|
(
|
|
'run_rep_def',
|
|
timeit.Timer(run_rep_def, setup=msg.Clear),
|
|
num_items * num_parses,
|
|
),
|
|
(
|
|
'run_rep_cust',
|
|
timeit.Timer(run_rep_cust, setup=msg.Clear),
|
|
num_items * num_parses,
|
|
),
|
|
(
|
|
'run_rep_unk',
|
|
timeit.Timer(run_rep_unk, setup=msg.Clear),
|
|
num_items * num_parses,
|
|
),
|
|
]
|
|
|
|
# Interleaved trials to minimize CPU frequency scaling / ordering bias
|
|
benchmark_samples = {name: [] for name, _, _ in benchmarks}
|
|
for _ in range(trials):
|
|
for name, timer, num_ops_per_batch in benchmarks:
|
|
total_ops = iterations * num_ops_per_batch
|
|
elapsed = timer.timeit(number=iterations)
|
|
benchmark_samples[name].append(elapsed * 1e6 / total_ops)
|
|
|
|
def stats(samples):
|
|
if len(samples) < 100:
|
|
raise ValueError(
|
|
f'Insufficient samples ({len(samples)}) for statistical'
|
|
' calculations. At least 100 samples are required.'
|
|
)
|
|
med_val = statistics.median(samples)
|
|
mean_val = statistics.mean(samples)
|
|
stdev_val = statistics.stdev(samples)
|
|
p99_val = statistics.quantiles(samples, n=100)[98]
|
|
return p99_val, med_val, mean_val, stdev_val
|
|
|
|
p99_def, med_def, mean_def, std_def = stats(benchmark_samples['run_def'])
|
|
p99_cust, med_cust, mean_cust, std_cust = stats(
|
|
benchmark_samples['run_cust']
|
|
)
|
|
p99_unk, med_unk, mean_unk, std_unk = stats(benchmark_samples['run_unk'])
|
|
p99_rep_def, med_rep_def, mean_rep_def, std_rep_def = stats(
|
|
benchmark_samples['run_rep_def']
|
|
)
|
|
p99_rep_cust, med_rep_cust, mean_rep_cust, std_rep_cust = stats(
|
|
benchmark_samples['run_rep_cust']
|
|
)
|
|
p99_rep_unk, med_rep_unk, mean_rep_unk, std_rep_unk = stats(
|
|
benchmark_samples['run_rep_unk']
|
|
)
|
|
|
|
print(
|
|
f'\n[BENCHMARK] ParseJsonDefault: med {med_def:.2f}'
|
|
f' us/op | p99 {p99_def:.2f} us/op | mean {mean_def:.2f} ±'
|
|
f' {std_def:.2f} us/op'
|
|
)
|
|
print(
|
|
f'[BENCHMARK] ParseJsonCustom: med {med_cust:.2f} us/op'
|
|
f' | p99 {p99_cust:.2f} us/op | mean {mean_cust:.2f} ± {std_cust:.2f}'
|
|
' us/op'
|
|
)
|
|
print(
|
|
'[BENCHMARK] ParseJsonUnknownIgnored: '
|
|
f' med {med_unk:.2f} us/op | p99 {p99_unk:.2f} us/op | mean'
|
|
f' {mean_unk:.2f} ± {std_unk:.2f} us/op'
|
|
)
|
|
print(
|
|
'[BENCHMARK] ParseRepeatedJsonDefault: '
|
|
f' med {med_rep_def:.2f} us/item | p99 {p99_rep_def:.2f} us/item |'
|
|
f' mean {mean_rep_def:.2f} ± {std_rep_def:.2f} us/item'
|
|
)
|
|
print(
|
|
'[BENCHMARK] ParseRepeatedJsonCustom: '
|
|
f' med {med_rep_cust:.2f} us/item | p99 {p99_rep_cust:.2f} us/item |'
|
|
f' mean {mean_rep_cust:.2f} ± {std_rep_cust:.2f} us/item'
|
|
)
|
|
print(
|
|
'[BENCHMARK] ParseRepeatedJsonUnknownIgnored:'
|
|
f' med {med_rep_unk:.2f} us/item | p99 {p99_rep_unk:.2f} us/item |'
|
|
f' mean {mean_rep_unk:.2f} ± {std_rep_unk:.2f} us/item'
|
|
)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main()
|