Skip to content

Commit dd71001

Browse files
committed
[WIP] Misc performance improvements
Signed-off-by: Juan Cruz Viotti <jv@jviotti.com>
1 parent e0cf433 commit dd71001

22 files changed

Lines changed: 1815 additions & 236 deletions

src/compiler/compile.cc

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@
1414
#include <utility> // std::move, std::pair
1515
#include <vector> // std::vector
1616

17+
#if defined(SOURCEMETA_BLAZE_DEBUG_TRACK_FLAGS)
18+
#include <cstdio> // stderr
19+
#include <functional> // std::function
20+
#include <print> // std::println
21+
#endif
22+
1723
#include "compile_helpers.h"
1824
#include "postprocess.h"
1925

@@ -147,6 +153,138 @@ auto schema_frame_populate_target_types(
147153
}
148154
}
149155

156+
auto is_evaluation_mark_instruction(
157+
const sourcemeta::blaze::InstructionIndex type) -> bool {
158+
using sourcemeta::blaze::InstructionIndex;
159+
switch (type) {
160+
case InstructionIndex::AssertionArrayPrefixEvaluate:
161+
case InstructionIndex::AssertionPropertyTypeEvaluate:
162+
case InstructionIndex::AssertionPropertyTypeStrictEvaluate:
163+
case InstructionIndex::AssertionPropertyTypeStrictAnyEvaluate:
164+
case InstructionIndex::Evaluate:
165+
case InstructionIndex::LogicalNotEvaluate:
166+
case InstructionIndex::LoopPropertiesUnevaluated:
167+
case InstructionIndex::LoopPropertiesUnevaluatedExcept:
168+
case InstructionIndex::LoopPropertiesEvaluate:
169+
case InstructionIndex::LoopPropertiesTypeEvaluate:
170+
case InstructionIndex::LoopPropertiesTypeStrictEvaluate:
171+
case InstructionIndex::LoopPropertiesTypeStrictAnyEvaluate:
172+
case InstructionIndex::LoopItemsUnevaluated:
173+
case InstructionIndex::ControlEvaluate:
174+
return true;
175+
default:
176+
return false;
177+
}
178+
}
179+
180+
auto contains_evaluation_marks(const sourcemeta::blaze::Instructions &steps,
181+
const std::vector<bool> &target_marks,
182+
const bool dynamic_marks) -> bool {
183+
using sourcemeta::blaze::InstructionIndex;
184+
for (const auto &step : steps) {
185+
if (is_evaluation_mark_instruction(step.type)) {
186+
return true;
187+
}
188+
189+
if (step.type == InstructionIndex::ControlJump &&
190+
target_marks[std::get<sourcemeta::blaze::ValueUnsignedInteger>(
191+
step.value)]) {
192+
return true;
193+
}
194+
195+
if (step.type == InstructionIndex::ControlDynamicAnchorJump &&
196+
dynamic_marks) {
197+
return true;
198+
}
199+
200+
if (contains_evaluation_marks(step.children, target_marks, dynamic_marks)) {
201+
return true;
202+
}
203+
}
204+
205+
return false;
206+
}
207+
208+
auto flag_evaluation_marks(sourcemeta::blaze::Instructions &steps,
209+
const std::vector<bool> &target_marks,
210+
const bool dynamic_marks) -> bool {
211+
using sourcemeta::blaze::InstructionIndex;
212+
bool result{false};
213+
for (auto &step : steps) {
214+
step.track =
215+
is_evaluation_mark_instruction(step.type) ||
216+
(step.type == InstructionIndex::ControlJump &&
217+
target_marks[std::get<sourcemeta::blaze::ValueUnsignedInteger>(
218+
step.value)]) ||
219+
(step.type == InstructionIndex::ControlDynamicAnchorJump &&
220+
dynamic_marks);
221+
if (flag_evaluation_marks(step.children, target_marks, dynamic_marks)) {
222+
step.track = true;
223+
}
224+
225+
result = result || step.track;
226+
}
227+
228+
return result;
229+
}
230+
231+
// Determine which instructions can produce or consume evaluation marks
232+
// somewhere in their subtree, including through static and dynamic jumps,
233+
// so that the evaluator only maintains the evaluate path on the spines that
234+
// lead to those instructions. Note that the evaluate path of every mark and
235+
// of every mark consumer is preserved exactly: all of their ancestors are
236+
// flagged, and only instructions that no mark or consumer can ever observe
237+
// stop pushing
238+
auto flag_evaluation_tracking(
239+
std::vector<sourcemeta::blaze::Instructions> &targets,
240+
const std::vector<std::pair<std::size_t, std::size_t>> &labels) -> void {
241+
std::vector<bool> target_marks(targets.size(), false);
242+
bool dynamic_marks{false};
243+
bool changed{true};
244+
while (changed) {
245+
changed = false;
246+
for (std::size_t index = 0; index < targets.size(); index++) {
247+
if (!target_marks[index] &&
248+
contains_evaluation_marks(targets[index], target_marks,
249+
dynamic_marks)) {
250+
target_marks[index] = true;
251+
changed = true;
252+
}
253+
}
254+
255+
if (!dynamic_marks &&
256+
std::ranges::any_of(labels, [&target_marks](const auto &label) {
257+
return target_marks[label.second];
258+
})) {
259+
dynamic_marks = true;
260+
changed = true;
261+
}
262+
}
263+
264+
for (auto &target : targets) {
265+
flag_evaluation_marks(target, target_marks, dynamic_marks);
266+
}
267+
268+
#if defined(SOURCEMETA_BLAZE_DEBUG_TRACK_FLAGS)
269+
std::size_t flagged{0};
270+
std::size_t total{0};
271+
// NOLINTNEXTLINE(misc-no-recursion)
272+
const std::function<void(const sourcemeta::blaze::Instructions &)> count{
273+
[&flagged, &total, &count](const sourcemeta::blaze::Instructions &steps) {
274+
for (const auto &step : steps) {
275+
total += 1;
276+
flagged += step.track ? 1 : 0;
277+
count(step.children);
278+
}
279+
}};
280+
for (const auto &target : targets) {
281+
count(target);
282+
}
283+
284+
std::println(stderr, "TRACK FLAGS: {} / {}", flagged, total);
285+
#endif
286+
}
287+
150288
} // namespace
151289

152290
namespace sourcemeta::blaze {
@@ -421,6 +559,11 @@ auto compile(const sourcemeta::core::JSON &schema,
421559
context.unevaluated, [](const auto &dependency) -> auto {
422560
return dependency.first.ends_with("unevaluatedItems");
423561
})};
562+
563+
if (track) {
564+
flag_evaluation_tracking(compiled_targets, labels_map);
565+
}
566+
424567
return {.dynamic = uses_dynamic_scopes,
425568
.track = track,
426569
.targets = std::move(compiled_targets),

src/compiler/default_compiler_draft3.h

Lines changed: 70 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@
88

99
#include <algorithm> // std::sort, std::ranges::any_of, std::ranges::all_of, std::find_if, std::ranges::none_of
1010
#include <cassert> // assert
11+
#include <map> // std::map
1112
#include <set> // std::set
12-
#include <utility> // std::move, std::to_underlying
13+
#include <string> // std::string
14+
#include <unordered_map> // std::unordered_map
15+
#include <utility> // std::move, std::to_underlying
1316

1417
#include "compile_helpers.h"
1518

@@ -104,10 +107,19 @@ compile_properties(const sourcemeta::blaze::Context &context,
104107
// we prefer to evaluate smaller subschemas first, in the hope of failing
105108
// earlier without spending a lot of time on other subschemas
106109
if (context.tweaks.properties_reorder) {
110+
std::unordered_map<std::string, std::size_t> template_sizes;
111+
template_sizes.reserve(properties.size());
112+
for (const auto &property : properties) {
113+
template_sizes.emplace(property.first,
114+
recursive_template_size(property.second));
115+
}
116+
107117
std::ranges::sort(
108-
properties, [&context](const auto &left, const auto &right) -> auto {
109-
const auto left_size{recursive_template_size(left.second)};
110-
const auto right_size{recursive_template_size(right.second)};
118+
properties,
119+
[&context, &template_sizes](const auto &left,
120+
const auto &right) -> auto {
121+
const auto left_size{template_sizes.at(left.first)};
122+
const auto right_size{template_sizes.at(right.first)};
111123
if (left_size == right_size) {
112124
const auto left_direct_enumeration{
113125
defines_direct_enumeration(left.second)};
@@ -556,10 +568,11 @@ auto compiler_draft3_applicator_properties_with_options(
556568
ValueNamedIndexes indexes;
557569
Instructions children;
558570
std::size_t cursor = 0;
571+
std::map<sourcemeta::core::JSON::String, std::size_t> child_positions;
559572

560573
for (auto &&[name, substeps] : compile_properties(
561574
context, schema_context, relative_dynamic_context(), current)) {
562-
indexes.emplace(name, cursor);
575+
child_positions.emplace(name, cursor);
563576

564577
if (track_evaluation) {
565578
substeps.push_back(make(
@@ -582,6 +595,17 @@ auto compiler_draft3_applicator_properties_with_options(
582595
cursor += 1;
583596
}
584597

598+
// Lay the lookup table out in schema declaration order, as instances
599+
// commonly follow it, which makes scanning the table for each instance
600+
// property terminate sooner on average
601+
for (const auto &entry :
602+
schema_context.schema.at(dynamic_context.keyword).as_object()) {
603+
const auto match{child_positions.find(entry.first)};
604+
if (match != child_positions.cend()) {
605+
indexes.emplace(entry.first, match->second);
606+
}
607+
}
608+
585609
if (context.mode == Mode::FastValidation && !track_evaluation &&
586610
!schema_context.schema.defines("patternProperties") &&
587611
schema_context.schema.defines("additionalProperties") &&
@@ -903,6 +927,47 @@ auto compiler_draft3_applicator_properties_with_options(
903927
for (auto &&step : substeps) {
904928
children.push_back(std::move(step));
905929
}
930+
} else if (
931+
context.mode == Mode::FastValidation &&
932+
std::ranges::all_of(substeps, [&name](const auto &step) {
933+
if (step.relative_instance_location.empty() ||
934+
!step.relative_instance_location.at(0).is_property() ||
935+
step.relative_instance_location.at(0).to_property() !=
936+
name) {
937+
return false;
938+
}
939+
940+
if (step.relative_instance_location.size() > 1) {
941+
return true;
942+
}
943+
944+
// These instructions require a non-empty
945+
// instance location of their own
946+
switch (step.type) {
947+
case InstructionIndex::AssertionPropertyType:
948+
case InstructionIndex::AssertionPropertyTypeEvaluate:
949+
case InstructionIndex::AssertionPropertyTypeStrict:
950+
case InstructionIndex::AssertionPropertyTypeStrictEvaluate:
951+
case InstructionIndex::AssertionPropertyTypeStrictAny:
952+
case InstructionIndex::AssertionPropertyTypeStrictAnyEvaluate:
953+
case InstructionIndex::ControlGroupWhenDefines:
954+
return false;
955+
default:
956+
return true;
957+
}
958+
})) {
959+
// When every step navigates into the property, we can resolve
960+
// it once and evaluate the steps against its value directly
961+
for (auto &step : substeps) {
962+
step.relative_instance_location =
963+
step.relative_instance_location.slice(1);
964+
}
965+
966+
children.push_back(make(sourcemeta::blaze::InstructionIndex::
967+
ControlGroupWhenDefinesResolved,
968+
context, schema_context,
969+
effective_dynamic_context,
970+
make_property(name), std::move(substeps)));
906971
} else {
907972
children.push_back(make(sourcemeta::blaze::InstructionIndex::
908973
ControlGroupWhenDefinesDirect,

0 commit comments

Comments
 (0)