Skip to content

Commit 706ea1f

Browse files
committed
Performance improvement changes
1 parent ee168ef commit 706ea1f

3 files changed

Lines changed: 95 additions & 46 deletions

File tree

cpp/include/hgraph/types/tsd.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,9 @@ namespace hgraph
215215

216216
VISITOR_SUPPORT()
217217

218-
void create(const value::ConstValueView &key);
218+
// Creates a new time series for the given key and returns it.
219+
// This allows get_or_create to avoid a second lookup after creation.
220+
value_type create(const value::ConstValueView &key);
219221

220222
[[nodiscard]] const value::TypeMeta* key_type_meta() const { return _key_type; }
221223

@@ -251,6 +253,9 @@ namespace hgraph
251253
map_type _modified_items;
252254
removed_items_map_type _removed_items; // Stores pair<value, was_valid>
253255
mutable map_type _valid_items_cache;
256+
mutable map_type _added_items_cache; // Instance member instead of static for thread safety
257+
mutable engine_time_t _valid_items_cache_time{MIN_DT}; // Track when valid_items cache was built
258+
mutable engine_time_t _added_items_cache_time{MIN_DT}; // Track when added_items cache was built
254259

255260
output_builder_s_ptr _ts_builder;
256261
output_builder_s_ptr _ts_ref_builder;
@@ -351,7 +356,8 @@ namespace hgraph
351356

352357
[[nodiscard]] engine_time_t last_modified_time() const override;
353358

354-
void create(const value::ConstValueView &key);
359+
// Creates a new time series for the given key and returns it.
360+
value_type create(const value::ConstValueView &key);
355361

356362
[[nodiscard]] TimeSeriesDictOutputImpl &output_t();
357363

@@ -406,6 +412,8 @@ namespace hgraph
406412
mutable map_type _added_items_cache;
407413
mutable map_type _removed_items_cache;
408414
mutable map_type _modified_items_cache;
415+
mutable engine_time_t _valid_items_cache_time{MIN_DT}; // Track when valid_items cache was built
416+
mutable engine_time_t _added_items_cache_time{MIN_DT}; // Track when added_items cache was built
409417
static inline map_type empty_{};
410418

411419
input_builder_s_ptr _ts_builder;

cpp/src/cpp/runtime/evaluation_engine.cpp

Lines changed: 28 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -279,19 +279,20 @@ namespace hgraph {
279279

280280
// Process all alarms that are due and adjust the next scheduled time
281281
while (!_alarms.empty()) {
282-
const auto &next_alarm = *_alarms.begin();
283-
if (now >= next_alarm.first) {
284-
auto alarm = *_alarms.begin();
285-
_alarms.erase(_alarms.begin());
282+
auto it = _alarms.begin();
283+
if (now >= it->first) {
284+
// Extract the alarm key before erasing to avoid redundant copy
285+
auto alarm_key = std::move(*it);
286+
_alarms.erase(it);
286287
next_scheduled_time = std::max(next_scheduled_time, evaluation_time() + MIN_TD);
287288

288-
auto cb = _alarm_callbacks.find(alarm);
289+
auto cb = _alarm_callbacks.find(alarm_key);
289290
if (cb != _alarm_callbacks.end()) {
290291
cb->second(next_scheduled_time);
291292
_alarm_callbacks.erase(cb);
292293
}
293-
} else if (next_scheduled_time > next_alarm.first) {
294-
next_scheduled_time = next_alarm.first;
294+
} else if (next_scheduled_time > it->first) {
295+
next_scheduled_time = it->first;
295296
break;
296297
} else {
297298
break;
@@ -342,12 +343,13 @@ namespace hgraph {
342343

343344
// Process alarms again after updating evaluation_time
344345
while (!_alarms.empty()) {
345-
const auto &next_alarm = *_alarms.begin();
346-
if (now >= next_alarm.first) {
347-
auto alarm = *_alarms.begin();
348-
_alarms.erase(_alarms.begin());
346+
auto it = _alarms.begin();
347+
if (now >= it->first) {
348+
// Extract the alarm key before erasing to avoid redundant copy
349+
auto alarm_key = std::move(*it);
350+
_alarms.erase(it);
349351

350-
auto cb = _alarm_callbacks.find(alarm);
352+
auto cb = _alarm_callbacks.find(alarm_key);
351353
if (cb != _alarm_callbacks.end()) {
352354
cb->second(evaluation_time());
353355
_alarm_callbacks.erase(cb);
@@ -447,30 +449,25 @@ namespace hgraph {
447449
}
448450

449451
void EvaluationEngineImpl::notify_before_evaluation() {
450-
// Copy the callback list and clear the original to prevent iterator invalidation
451-
auto todo = std::move(_before_evaluation_notification);
452-
_before_evaluation_notification.clear();
453-
454-
for (auto &notification_receiver: todo) {
455-
notification_receiver();
456-
// If new notifications were added during callback execution, process them recursively
457-
if (!_before_evaluation_notification.empty()) {
458-
notify_before_evaluation();
452+
// Process all notifications iteratively, including any added during callback execution
453+
while (!_before_evaluation_notification.empty()) {
454+
auto todo = std::move(_before_evaluation_notification);
455+
_before_evaluation_notification.clear();
456+
457+
for (auto &notification_receiver: todo) {
458+
notification_receiver();
459459
}
460460
}
461461
}
462462

463463
void EvaluationEngineImpl::notify_after_evaluation() {
464-
// Copy the callback list and clear the original, matching Python's behavior.
465-
// This prevents iterator invalidation if callbacks add more callbacks.
466-
auto todo = std::move(_after_evaluation_notification);
467-
_after_evaluation_notification.clear();
468-
469-
for (auto it = todo.rbegin(); it != todo.rend(); ++it) {
470-
(*it)();
471-
// If new notifications were added during callback execution, process them recursively
472-
if (!_after_evaluation_notification.empty()) {
473-
notify_after_evaluation();
464+
// Process all notifications iteratively in reverse order, including any added during callback execution
465+
while (!_after_evaluation_notification.empty()) {
466+
auto todo = std::move(_after_evaluation_notification);
467+
_after_evaluation_notification.clear();
468+
469+
for (auto it = todo.rbegin(); it != todo.rend(); ++it) {
470+
(*it)();
474471
}
475472
}
476473
}

cpp/src/cpp/types/tsd.cpp

Lines changed: 57 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -435,27 +435,47 @@ namespace hgraph
435435
}
436436

437437
const TimeSeriesDictOutputImpl::map_type &TimeSeriesDictOutputImpl::valid_items() const {
438-
// Rebuild cache each call to ensure freshness
438+
// Use cached result if cache was built at or after our last modification time
439+
// This avoids expensive iteration and key cloning when nothing changed
440+
auto lmt = last_modified_time();
441+
if (_valid_items_cache_time >= lmt && !_valid_items_cache.empty()) {
442+
return _valid_items_cache;
443+
}
444+
445+
// Rebuild cache
439446
_valid_items_cache.clear();
440447
for (const auto &[pv_key, val] : _ts_values) {
441448
if (val->valid()) {
442449
_valid_items_cache.emplace(pv_key.const_view().clone(), val);
443450
}
444451
}
452+
_valid_items_cache_time = lmt;
445453
return _valid_items_cache;
446454
}
447455

448456
const TimeSeriesDictOutputImpl::map_type &TimeSeriesDictOutputImpl::added_items() const {
449-
// Return items that were added, fetched from the key_set's added view
450-
static map_type added_cache; // Static to persist across calls
451-
added_cache.clear();
457+
// Early return if no items were added (avoids iteration entirely)
458+
if (key_set().added_view().empty()) {
459+
_added_items_cache.clear();
460+
return _added_items_cache;
461+
}
462+
463+
// Use cached result if key_set hasn't been modified since cache was built
464+
auto ks_lmt = key_set().last_modified_time();
465+
if (_added_items_cache_time >= ks_lmt && !_added_items_cache.empty()) {
466+
return _added_items_cache;
467+
}
468+
469+
// Rebuild cache - uses instance member instead of static for thread safety
470+
_added_items_cache.clear();
452471
for (auto elem : key_set().added_view()) {
453472
auto it = _ts_values.find(elem);
454473
if (it != _ts_values.end()) {
455-
added_cache.emplace(elem.clone(), it->second);
474+
_added_items_cache.emplace(elem.clone(), it->second);
456475
}
457476
}
458-
return added_cache;
477+
_added_items_cache_time = ks_lmt;
478+
return _added_items_cache;
459479
}
460480

461481
TimeSeriesSetOutput &TimeSeriesDictOutputImpl::key_set() { return *_key_set; }
@@ -535,11 +555,11 @@ namespace hgraph
535555

536556
TimeSeriesDictOutputImpl::value_type TimeSeriesDictOutputImpl::get_or_create(const value::ConstValueView &key) {
537557
auto it = _ts_values.find(key);
538-
if (it == _ts_values.end()) {
539-
create(key);
540-
it = _ts_values.find(key);
558+
if (it != _ts_values.end()) {
559+
return it->second;
541560
}
542-
return it->second;
561+
// Create returns the new item, avoiding a second hash lookup
562+
return create(key);
543563
}
544564

545565
bool TimeSeriesDictOutputImpl::has_reference() const { return _ts_builder->has_reference(); }
@@ -650,17 +670,37 @@ namespace hgraph
650670
}
651671

652672
const TimeSeriesDictInputImpl::map_type &TimeSeriesDictInputImpl::valid_items() const {
653-
// Rebuild cache each call to ensure freshness
673+
// Use cached result if cache was built at or after our last modification time
674+
// This avoids expensive iteration and key cloning when nothing changed
675+
auto lmt = last_modified_time();
676+
if (_valid_items_cache_time >= lmt && !_valid_items_cache.empty()) {
677+
return _valid_items_cache;
678+
}
679+
680+
// Rebuild cache
654681
_valid_items_cache.clear();
655682
for (const auto &[pv_key, val] : _ts_values) {
656683
if (val->valid()) {
657684
_valid_items_cache.emplace(pv_key.const_view().clone(), val);
658685
}
659686
}
687+
_valid_items_cache_time = lmt;
660688
return _valid_items_cache;
661689
}
662690

663691
const TimeSeriesDictInputImpl::map_type &TimeSeriesDictInputImpl::added_items() const {
692+
// Early return if no items were added (avoids collect_added() and iteration)
693+
if (!has_added()) {
694+
_added_items_cache.clear();
695+
return _added_items_cache;
696+
}
697+
698+
// Use cached result if key_set hasn't been modified since cache was built
699+
auto ks_lmt = key_set().last_modified_time();
700+
if (_added_items_cache_time >= ks_lmt && !_added_items_cache.empty()) {
701+
return _added_items_cache;
702+
}
703+
664704
// Rebuild cache using key_set's collect_added() which handles _prev_output
665705
_added_items_cache.clear();
666706
auto added_keys = key_set().collect_added();
@@ -670,6 +710,7 @@ namespace hgraph
670710
_added_items_cache.emplace(elem.clone(), it->second);
671711
}
672712
}
713+
_added_items_cache_time = ks_lmt;
673714
return _added_items_cache;
674715
}
675716

@@ -1074,17 +1115,18 @@ namespace hgraph
10741115
BaseTimeSeriesInput::notify_parent(this, modified_time);
10751116
}
10761117

1077-
void TimeSeriesDictInputImpl::create(const value::ConstValueView &key_view) {
1118+
TimeSeriesDictInputImpl::value_type TimeSeriesDictInputImpl::create(const value::ConstValueView &key_view) {
10781119
auto item{_ts_builder->make_instance(this)};
10791120
// For non-peered inputs that are active, make the newly created item active too
10801121
// This ensures proper notification chain for fast non-peer TSD scenarios
10811122
if (!has_peer() and active()) { item->make_active(); }
10821123
// Use emplace with cloned key for move-only PlainValue storage
10831124
_ts_values.emplace(key_view.clone(), item);
10841125
_add_key_value(key_view, item);
1126+
return item; // Return the created item
10851127
}
10861128

1087-
void TimeSeriesDictOutputImpl::create(const value::ConstValueView &key_view) {
1129+
TimeSeriesDictOutputImpl::value_type TimeSeriesDictOutputImpl::create(const value::ConstValueView &key_view) {
10881130
// Add key to TSS (already Value-based)
10891131
key_set().add(key_view);
10901132

@@ -1114,6 +1156,8 @@ namespace hgraph
11141156
}
11151157
});
11161158
}
1159+
1160+
return item; // Return the created item to avoid second lookup in get_or_create
11171161
}
11181162

11191163
void TimeSeriesDictOutputImpl::add_key_observer(TSDKeyObserver *observer) {

0 commit comments

Comments
 (0)