@@ -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