Skip to content

Commit

Permalink
Prioritize smaller subschemas when unrolling properties (#1264)
Browse files Browse the repository at this point in the history
Signed-off-by: Juan Cruz Viotti <[email protected]>
  • Loading branch information
jviotti authored Oct 4, 2024
1 parent 6294999 commit b61df71
Show file tree
Hide file tree
Showing 2 changed files with 111 additions and 2 deletions.
9 changes: 7 additions & 2 deletions src/jsonschema/default_compiler_draft4.h
Original file line number Diff line number Diff line change
Expand Up @@ -423,10 +423,15 @@ auto compiler_draft4_applicator_properties_conditional_annotation(
}
}

// To guarantee order
// In many cases, `properties` have some subschemas that are small
// and some subschemas that are large. To attempt to improve performance,
// we prefer to evaluate smaller subschemas first, in the hope of failing
// earlier without spending a lot of time on other subschemas
std::sort(properties.begin(), properties.end(),
[](const auto &left, const auto &right) {
return left.first < right.first;
return (left.second.size() == right.second.size())
? (left.first < right.first)
: (left.second.size() < right.second.size());
});

// There are two ways to compile `properties` depending on whether
Expand Down
104 changes: 104 additions & 0 deletions test/evaluator/evaluator_draft4_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1200,6 +1200,110 @@ TEST(JSONSchema_evaluator_draft4, properties_6) {
"against the single defined property subschema");
}

TEST(JSONSchema_evaluator_draft4, properties_7) {
const sourcemeta::jsontoolkit::JSON schema{
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "http://json-schema.org/draft-04/schema#",
"properties": {
"foo": { "type": "string", "pattern": "^a" },
"bar": { "type": "integer" }
}
})JSON")};

const auto compiled_schema{sourcemeta::jsontoolkit::compile(
schema, sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver,
sourcemeta::jsontoolkit::default_schema_compiler)};

const sourcemeta::jsontoolkit::JSON instance{
sourcemeta::jsontoolkit::parse("{ \"bar\": 2, \"foo\": \"abc\" }")};

EVALUATE_WITH_TRACE_FAST_SUCCESS(compiled_schema, instance, 4);

EVALUATE_TRACE_PRE(0, LogicalAnd, "/properties", "#/properties", "");

// Note we evaluate "bar" before "foo" because the number of instructions
// in "bar" is less
EVALUATE_TRACE_PRE(1, AssertionPropertyTypeStrict, "/properties/bar/type",
"#/properties/bar/type", "/bar");
EVALUATE_TRACE_PRE(2, AssertionTypeStrict, "/properties/foo/type",
"#/properties/foo/type", "/foo");
EVALUATE_TRACE_PRE(3, AssertionRegex, "/properties/foo/pattern",
"#/properties/foo/pattern", "/foo");

EVALUATE_TRACE_POST_SUCCESS(0, AssertionPropertyTypeStrict,
"/properties/bar/type", "#/properties/bar/type",
"/bar");
EVALUATE_TRACE_POST_SUCCESS(1, AssertionTypeStrict, "/properties/foo/type",
"#/properties/foo/type", "/foo");
EVALUATE_TRACE_POST_SUCCESS(2, AssertionRegex, "/properties/foo/pattern",
"#/properties/foo/pattern", "/foo");
EVALUATE_TRACE_POST_SUCCESS(3, LogicalAnd, "/properties", "#/properties", "");

EVALUATE_TRACE_POST_DESCRIBE(instance, 0,
"The value was expected to be of type integer");
EVALUATE_TRACE_POST_DESCRIBE(instance, 1,
"The value was expected to be of type string");
EVALUATE_TRACE_POST_DESCRIBE(instance, 2,
"The string value \"abc\" was expected to match "
"the regular expression \"^a\"");
EVALUATE_TRACE_POST_DESCRIBE(instance, 3,
"The object value was expected to validate "
"against the defined properties subschemas");
}

TEST(JSONSchema_evaluator_draft4, properties_8) {
const sourcemeta::jsontoolkit::JSON schema{
sourcemeta::jsontoolkit::parse(R"JSON({
"$schema": "http://json-schema.org/draft-04/schema#",
"properties": {
"bar": { "type": "string", "pattern": "^a" },
"foo": { "type": "integer" }
}
})JSON")};

const auto compiled_schema{sourcemeta::jsontoolkit::compile(
schema, sourcemeta::jsontoolkit::default_schema_walker,
sourcemeta::jsontoolkit::official_resolver,
sourcemeta::jsontoolkit::default_schema_compiler)};

const sourcemeta::jsontoolkit::JSON instance{
sourcemeta::jsontoolkit::parse("{ \"foo\": 2, \"bar\": \"abc\" }")};

EVALUATE_WITH_TRACE_FAST_SUCCESS(compiled_schema, instance, 4);

EVALUATE_TRACE_PRE(0, LogicalAnd, "/properties", "#/properties", "");

// Note we evaluate "foo" before "bar" because the number of instructions
// in "foo" is less
EVALUATE_TRACE_PRE(1, AssertionPropertyTypeStrict, "/properties/foo/type",
"#/properties/foo/type", "/foo");
EVALUATE_TRACE_PRE(2, AssertionTypeStrict, "/properties/bar/type",
"#/properties/bar/type", "/bar");
EVALUATE_TRACE_PRE(3, AssertionRegex, "/properties/bar/pattern",
"#/properties/bar/pattern", "/bar");

EVALUATE_TRACE_POST_SUCCESS(0, AssertionPropertyTypeStrict,
"/properties/foo/type", "#/properties/foo/type",
"/foo");
EVALUATE_TRACE_POST_SUCCESS(1, AssertionTypeStrict, "/properties/bar/type",
"#/properties/bar/type", "/bar");
EVALUATE_TRACE_POST_SUCCESS(2, AssertionRegex, "/properties/bar/pattern",
"#/properties/bar/pattern", "/bar");
EVALUATE_TRACE_POST_SUCCESS(3, LogicalAnd, "/properties", "#/properties", "");

EVALUATE_TRACE_POST_DESCRIBE(instance, 0,
"The value was expected to be of type integer");
EVALUATE_TRACE_POST_DESCRIBE(instance, 1,
"The value was expected to be of type string");
EVALUATE_TRACE_POST_DESCRIBE(instance, 2,
"The string value \"abc\" was expected to match "
"the regular expression \"^a\"");
EVALUATE_TRACE_POST_DESCRIBE(instance, 3,
"The object value was expected to validate "
"against the defined properties subschemas");
}

TEST(JSONSchema_evaluator_draft4, pattern_1) {
const sourcemeta::jsontoolkit::JSON schema{
sourcemeta::jsontoolkit::parse(R"JSON({
Expand Down

4 comments on commit b61df71

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark (macos/llvm)

Benchmark suite Current: b61df71 Previous: 6294999 Ratio
JSON_Array_Of_Objects_Unique 4228.983833536213 ns/iter 3937.528900839911 ns/iter 1.07
JSONSchema_Validate_Draft4_Meta_1_No_Callback 756.8970007657384 ns/iter 806.1586002987991 ns/iter 0.94
JSONSchema_Validate_Draft4_Required_Properties 944.3472298982031 ns/iter 1009.7666566931343 ns/iter 0.94
JSONSchema_Validate_Draft4_Many_Optional_Properties_Minimal_Match 163.10640559463337 ns/iter 154.51073469189382 ns/iter 1.06
JSONSchema_Validate_Draft4_Few_Optional_Properties_Minimal_Match 110.2332090922805 ns/iter 113.08618920983065 ns/iter 0.97
JSONSchema_Validate_Draft4_Items_Schema 2997.754831915101 ns/iter 2980.5212906220127 ns/iter 1.01
JSONSchema_Validate_Draft4_Nested_Object 1425.7948252337903 ns/iter 1444.7322346039996 ns/iter 0.99
JSONSchema_Validate_Draft4_Properties_Triad_Optional 1407.773732902339 ns/iter 1418.578794682174 ns/iter 0.99
JSONSchema_Validate_Draft4_Properties_Triad_Closed 1127.4141609006783 ns/iter 1067.8162157467318 ns/iter 1.06
JSONSchema_Validate_Draft4_Properties_Triad_Required 1447.88217283138 ns/iter 1391.0498854596124 ns/iter 1.04
JSONSchema_Validate_Draft4_Non_Recursive_Ref 199.9800586967782 ns/iter 196.57118449831242 ns/iter 1.02
JSONSchema_Validate_Draft4_Pattern_Properties_True 1422.2993634325308 ns/iter 1402.889794701279 ns/iter 1.01
JSONSchema_Validate_Draft4_Ref_To_Single_Property 118.82817779364315 ns/iter 108.90832806301363 ns/iter 1.09
JSONSchema_Validate_Draft4_Additional_Properties_Type 431.2833290797157 ns/iter 368.3868667057541 ns/iter 1.17
JSONSchema_Validate_Draft4_Nested_Oneof 388.48040997733597 ns/iter 384.23050258074966 ns/iter 1.01
JSONSchema_Validate_Draft6_Property_Names 798.4428659732785 ns/iter 797.7241642313325 ns/iter 1.00
JSONSchema_Validate_Draft7_If_Then_Else 175.58734697807049 ns/iter 186.49332325872 ns/iter 0.94
JSONSchema_Compiler_Draft6_AdaptiveCard 3289723541.9999332 ns/iter 3277179000.0000463 ns/iter 1.00

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark (linux/llvm)

Benchmark suite Current: b61df71 Previous: 6294999 Ratio
JSON_Array_Of_Objects_Unique 2125.466187203498 ns/iter 2182.9740027386756 ns/iter 0.97
JSONSchema_Validate_Draft4_Meta_1_No_Callback 969.8297490641231 ns/iter 963.8368892162929 ns/iter 1.01
JSONSchema_Validate_Draft4_Required_Properties 1550.6650848154436 ns/iter 1550.035119739843 ns/iter 1.00
JSONSchema_Validate_Draft4_Many_Optional_Properties_Minimal_Match 178.66687902311585 ns/iter 182.64466437376754 ns/iter 0.98
JSONSchema_Validate_Draft4_Few_Optional_Properties_Minimal_Match 127.76845643075254 ns/iter 129.19842384007455 ns/iter 0.99
JSONSchema_Validate_Draft4_Items_Schema 4021.3208777623354 ns/iter 4092.162961684643 ns/iter 0.98
JSONSchema_Validate_Draft4_Nested_Object 1623.690513745676 ns/iter 1613.0874931692542 ns/iter 1.01
JSONSchema_Validate_Draft4_Properties_Triad_Optional 1830.8155239609716 ns/iter 1812.4824625900078 ns/iter 1.01
JSONSchema_Validate_Draft4_Properties_Triad_Closed 1522.9760682388805 ns/iter 1537.8913410297898 ns/iter 0.99
JSONSchema_Validate_Draft4_Properties_Triad_Required 1918.1837939895374 ns/iter 1905.288624866176 ns/iter 1.01
JSONSchema_Validate_Draft4_Non_Recursive_Ref 484.9073095400828 ns/iter 480.4294362178446 ns/iter 1.01
JSONSchema_Validate_Draft4_Pattern_Properties_True 2485.7303650443373 ns/iter 2452.880311354559 ns/iter 1.01
JSONSchema_Validate_Draft4_Ref_To_Single_Property 130.3317448280249 ns/iter 132.41329101264972 ns/iter 0.98
JSONSchema_Validate_Draft4_Additional_Properties_Type 607.4604941581644 ns/iter 597.9042439759944 ns/iter 1.02
JSONSchema_Validate_Draft4_Nested_Oneof 501.29739099998005 ns/iter 489.7666872682761 ns/iter 1.02
JSONSchema_Validate_Draft6_Property_Names 1266.1937730728878 ns/iter 1228.6577636899522 ns/iter 1.03
JSONSchema_Validate_Draft7_If_Then_Else 216.23716011899097 ns/iter 215.31431444926525 ns/iter 1.00
JSONSchema_Compiler_Draft6_AdaptiveCard 5718458451.999936 ns/iter 5848453121.999909 ns/iter 0.98

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark (linux/gcc)

Benchmark suite Current: b61df71 Previous: 6294999 Ratio
JSONSchema_Compiler_Draft6_AdaptiveCard 6337498589.000006 ns/iter 6433234743.000071 ns/iter 0.99
JSONSchema_Validate_Draft4_Meta_1_No_Callback 1071.00218471577 ns/iter 1069.5909789852094 ns/iter 1.00
JSONSchema_Validate_Draft4_Required_Properties 2213.849232737453 ns/iter 2215.572399811603 ns/iter 1.00
JSONSchema_Validate_Draft4_Many_Optional_Properties_Minimal_Match 189.939282994826 ns/iter 195.11242584902269 ns/iter 0.97
JSONSchema_Validate_Draft4_Few_Optional_Properties_Minimal_Match 133.7014831079329 ns/iter 136.2061426688963 ns/iter 0.98
JSONSchema_Validate_Draft4_Items_Schema 3323.038183948757 ns/iter 3473.737210692548 ns/iter 0.96
JSONSchema_Validate_Draft4_Nested_Object 1728.2659043137805 ns/iter 1754.7800964004336 ns/iter 0.98
JSONSchema_Validate_Draft4_Properties_Triad_Optional 1664.5526159290514 ns/iter 1714.4558625453071 ns/iter 0.97
JSONSchema_Validate_Draft4_Properties_Triad_Closed 1350.834848610817 ns/iter 1406.3312669506115 ns/iter 0.96
JSONSchema_Validate_Draft4_Properties_Triad_Required 1752.186969079661 ns/iter 1790.8074309908486 ns/iter 0.98
JSONSchema_Validate_Draft4_Non_Recursive_Ref 468.0941453933242 ns/iter 480.05388233589053 ns/iter 0.98
JSONSchema_Validate_Draft4_Pattern_Properties_True 2260.2885569095783 ns/iter 2318.8436514762325 ns/iter 0.97
JSONSchema_Validate_Draft4_Ref_To_Single_Property 143.19506605534158 ns/iter 142.1822246081362 ns/iter 1.01
JSONSchema_Validate_Draft4_Additional_Properties_Type 1109.9155069693222 ns/iter 1144.26057243759 ns/iter 0.97
JSONSchema_Validate_Draft4_Nested_Oneof 429.05788632978897 ns/iter 449.77776658022447 ns/iter 0.95
JSONSchema_Validate_Draft6_Property_Names 1653.3827042390626 ns/iter 1674.786142951919 ns/iter 0.99
JSONSchema_Validate_Draft7_If_Then_Else 203.44449435695464 ns/iter 210.9531054678023 ns/iter 0.96
JSON_Array_Of_Objects_Unique 3192.926992207455 ns/iter 3213.7175289417746 ns/iter 0.99

This comment was automatically generated by workflow using github-action-benchmark.

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Benchmark (windows/msvc)

Benchmark suite Current: b61df71 Previous: 6294999 Ratio
JSON_Array_Of_Objects_Unique 5175.542857142342 ns/iter 5262.526999999864 ns/iter 0.98
JSONSchema_Validate_Draft4_Meta_1_No_Callback 2275.7921874998033 ns/iter 2280.126026645512 ns/iter 1.00
JSONSchema_Validate_Draft4_Required_Properties 2082.5222349577925 ns/iter 1930.8094540285438 ns/iter 1.08
JSONSchema_Validate_Draft4_Many_Optional_Properties_Minimal_Match 549.6886000000814 ns/iter 538.8468999999532 ns/iter 1.02
JSONSchema_Validate_Draft4_Few_Optional_Properties_Minimal_Match 405.59359796460564 ns/iter 403.43519181089005 ns/iter 1.01
JSONSchema_Validate_Draft4_Items_Schema 6346.158035714415 ns/iter 6448.38482142924 ns/iter 0.98
JSONSchema_Validate_Draft4_Nested_Object 3838.726180846141 ns/iter 3821.128906250796 ns/iter 1.00
JSONSchema_Validate_Draft4_Properties_Triad_Optional 5329.37899999979 ns/iter 5247.669000000315 ns/iter 1.02
JSONSchema_Validate_Draft4_Properties_Triad_Closed 4358.736250000561 ns/iter 4307.124375000626 ns/iter 1.01
JSONSchema_Validate_Draft4_Properties_Triad_Required 5437.367999999196 ns/iter 5352.0720000005895 ns/iter 1.02
JSONSchema_Validate_Draft4_Non_Recursive_Ref 559.1048214285925 ns/iter 564.8828571427852 ns/iter 0.99
JSONSchema_Validate_Draft4_Pattern_Properties_True 7977.271083610246 ns/iter 7874.395089285509 ns/iter 1.01
JSONSchema_Validate_Draft4_Ref_To_Single_Property 413.17045741501 ns/iter 410.63208434675647 ns/iter 1.01
JSONSchema_Validate_Draft4_Additional_Properties_Type 798.640759535353 ns/iter 795.0976562501719 ns/iter 1.00
JSONSchema_Validate_Draft4_Nested_Oneof 1066.4462499999418 ns/iter 1070.2284375000204 ns/iter 1.00
JSONSchema_Validate_Draft6_Property_Names 1847.3172743905548 ns/iter 1866.8470534505484 ns/iter 0.99
JSONSchema_Validate_Draft7_If_Then_Else 566.4353571427837 ns/iter 554.5963392858002 ns/iter 1.02
JSONSchema_Compiler_Draft6_AdaptiveCard 11459921900.00004 ns/iter 11150981799.999954 ns/iter 1.03

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.