Skip to content

Commit da308cb

Browse files
authored
Faster glz::validate_json (#1478)
* Optimization JSON validation * total rework * JSON specific * use ctx * strip iterator type and cleanup * Update validate.hpp * More [[unlikely]] and fix for jsonrpc * use switch case * More [[unlikely]] * unicode surrogate pair validation * Padded and faster padded string validation * remove validate.hpp * More optimized match * reverting test code
1 parent 517b22b commit da308cb

File tree

4 files changed

+79
-11
lines changed

4 files changed

+79
-11
lines changed

include/glaze/json/read.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -2714,7 +2714,7 @@ namespace glz
27142714
{
27152715
context ctx{};
27162716
glz::skip skip_value{};
2717-
return read<opts{.validate_skipped = true, .validate_trailing_whitespace = true}>(
2717+
return read<opts{.comments = true, .validate_skipped = true, .validate_trailing_whitespace = true}>(
27182718
skip_value, std::forward<Buffer>(buffer), ctx);
27192719
}
27202720

include/glaze/json/skip.hpp

+59-8
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ namespace glz::detail
1111
struct skip_value<JSON>
1212
{
1313
template <opts Opts>
14+
requires (not Opts.comments)
15+
GLZ_ALWAYS_INLINE static void op(is_context auto&& ctx, auto&& it, auto&& end) noexcept;
16+
17+
template <opts Opts>
18+
requires (bool(Opts.comments))
1419
GLZ_ALWAYS_INLINE static void op(is_context auto&& ctx, auto&& it, auto&& end) noexcept;
1520
};
1621

@@ -96,10 +101,11 @@ namespace glz::detail
96101
}
97102

98103
template <opts Opts>
104+
requires (not Opts.comments)
99105
GLZ_ALWAYS_INLINE void skip_value<JSON>::op(is_context auto&& ctx, auto&& it, auto&& end) noexcept
100106
{
101-
if constexpr (!Opts.validate_skipped) {
102-
if constexpr (!has_ws_handled(Opts)) {
107+
if constexpr (not Opts.validate_skipped) {
108+
if constexpr (not has_ws_handled(Opts)) {
103109
GLZ_SKIP_WS();
104110
}
105111
while (true) {
@@ -123,11 +129,6 @@ namespace glz::detail
123129
if (bool(ctx.error)) [[unlikely]]
124130
return;
125131
break;
126-
case '/':
127-
skip_comment(ctx, it, end);
128-
if (bool(ctx.error)) [[unlikely]]
129-
return;
130-
continue;
131132
case ',':
132133
case '}':
133134
case ']':
@@ -146,7 +147,7 @@ namespace glz::detail
146147
}
147148
}
148149
else {
149-
if constexpr (!has_ws_handled(Opts)) {
150+
if constexpr (not has_ws_handled(Opts)) {
150151
GLZ_SKIP_WS();
151152
}
152153
switch (*it) {
@@ -187,6 +188,56 @@ namespace glz::detail
187188
}
188189
}
189190
}
191+
192+
template <opts Opts>
193+
requires (bool(Opts.comments))
194+
GLZ_ALWAYS_INLINE void skip_value<JSON>::op(is_context auto&& ctx, auto&& it, auto&& end) noexcept
195+
{
196+
if constexpr (not has_ws_handled(Opts)) {
197+
GLZ_SKIP_WS();
198+
}
199+
switch (*it) {
200+
case '{': {
201+
skip_object<Opts>(ctx, it, end);
202+
break;
203+
}
204+
case '[': {
205+
skip_array<Opts>(ctx, it, end);
206+
break;
207+
}
208+
case '"': {
209+
skip_string<Opts>(ctx, it, end);
210+
break;
211+
}
212+
case '/': {
213+
skip_comment(ctx, it, end);
214+
if (bool(ctx.error)) [[unlikely]]
215+
return;
216+
}
217+
case 'n': {
218+
++it;
219+
match<"ull", Opts>(ctx, it, end);
220+
break;
221+
}
222+
case 'f': {
223+
++it;
224+
match<"alse", Opts>(ctx, it, end);
225+
break;
226+
}
227+
case 't': {
228+
++it;
229+
match<"rue", Opts>(ctx, it, end);
230+
break;
231+
}
232+
case '\0': {
233+
ctx.error = error_code::unexpected_end;
234+
break;
235+
}
236+
default: {
237+
skip_number<Opts>(ctx, it, end);
238+
}
239+
}
240+
}
190241

191242
// parse_value is used for JSON pointer reading
192243
// we want the JSON pointer access to not care about trailing whitespace

include/glaze/util/parse.hpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,8 @@ namespace glz::detail
448448
requires(has_is_padded(Opts) && str.size() <= padding_bytes)
449449
GLZ_ALWAYS_INLINE void match(is_context auto&& ctx, auto&& it, auto&&) noexcept
450450
{
451-
if (!compare<str.size()>(it, str.value)) [[unlikely]] {
451+
static constexpr auto S = str.sv();
452+
if (not comparitor<S>(it)) [[unlikely]] {
452453
ctx.error = error_code::syntax_error;
453454
}
454455
else [[likely]] {
@@ -461,7 +462,8 @@ namespace glz::detail
461462
GLZ_ALWAYS_INLINE void match(is_context auto&& ctx, auto&& it, auto&& end) noexcept
462463
{
463464
const auto n = size_t(end - it);
464-
if ((n < str.size()) || !compare<str.size()>(it, str.value)) [[unlikely]] {
465+
static constexpr auto S = str.sv();
466+
if ((n < str.size()) || not comparitor<S>(it)) [[unlikely]] {
465467
ctx.error = error_code::syntax_error;
466468
}
467469
else [[likely]] {

tests/json_performance/json_performance.cpp

+15
Original file line numberDiff line numberDiff line change
@@ -1261,6 +1261,21 @@ auto generic_tester()
12611261
t1 = std::chrono::steady_clock::now();
12621262

12631263
r.json_read = std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count() * 1e-6;
1264+
1265+
// validate performance
1266+
1267+
t0 = std::chrono::steady_clock::now();
1268+
1269+
for (size_t i = 0; i < iterations; ++i) {
1270+
if (glz::validate_json(buffer)) {
1271+
std::cout << "glaze error!\n";
1272+
break;
1273+
}
1274+
}
1275+
1276+
t1 = std::chrono::steady_clock::now();
1277+
1278+
std::cout << "validation time: " << std::chrono::duration_cast<std::chrono::microseconds>(t1 - t0).count() * 1e-6 << '\n';
12641279

12651280
// beve write performance
12661281

0 commit comments

Comments
 (0)