-
Notifications
You must be signed in to change notification settings - Fork 16
Add caching to build_component_topic_map with graph invalidation #188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3cb0500
5560e80
9ee813d
a6094eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -278,18 +278,24 @@ class NativeTopicSampler { | |
| * @param topic_name Full topic path | ||
| * @return true if this is a system topic that should be filtered | ||
| */ | ||
| static bool is_system_topic(const std::string & topic_name); | ||
| static bool is_system_topic(const std::string & topic_name); | ||
|
|
||
| private: | ||
| /** | ||
| * @brief Get the message type for a topic from the graph | ||
| */ | ||
| std::string get_topic_type(const std::string & topic_name); | ||
| private: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| std::string get_topic_type(const std::string & topic_name); | ||
|
Comment on lines
+281
to
+284
|
||
|
|
||
| rclcpp::Node * node_; | ||
|
|
||
| /// Native JSON serializer for topic deserialization | ||
| std::shared_ptr<ros2_medkit_serialization::JsonSerializer> serializer_; | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
|
|
||
| // Getter for cached component-topic map | ||
| // Returns a copy to avoid exposing internal data while the mutex is released. | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. New |
||
| std::map<std::string, ComponentTopics> | ||
| get_component_topic_map(); | ||
|
|
||
| // --- Component topic map cache --- | ||
| std::map<std::string, ComponentTopics> topic_map_cache_; | ||
| size_t cached_graph_change_count_{0}; | ||
| mutable std::mutex topic_map_mutex_; | ||
ganeshkarthik016 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }; | ||
|
|
||
| } // namespace ros2_medkit_gateway | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -486,11 +486,43 @@ std::map<std::string, ComponentTopics> NativeTopicSampler::build_component_topic | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RCLCPP_DEBUG(node_->get_logger(), "Built topic map for %zu components", component_map.size()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return component_map; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::map<std::string, ComponentTopics> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing blank line between |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| NativeTopicSampler::get_component_topic_map() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::lock_guard<std::mutex> lock(topic_map_mutex_); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Compute a lightweight fingerprint of the ROS graph (topics+types) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Note: ROS 2 does not expose a simple graph change counter; using | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // get_topic_names_and_types() and hashing the result is a reliable | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // way to detect graph changes that affect topic/component mapping. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| auto all_topics = node_->get_topic_names_and_types(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t current_graph_fingerprint = 1469598103934665603ULL; // FNV-1a seed-ish | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Magic number |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| std::hash<std::string> hasher; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Incorporate topic count | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current_graph_fingerprint ^= all_topics.size() + 0x9e3779b97f4a7c15ULL + (current_graph_fingerprint << 6) + (current_graph_fingerprint >> 2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The fingerprint only hashes topic names+types, but |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (const auto & kv : all_topics) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const auto & topic = kv.first; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const auto & types = kv.second; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current_graph_fingerprint ^= hasher(topic) + 0x9e3779b97f4a7c15ULL + (current_graph_fingerprint << 6) + (current_graph_fingerprint >> 2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!types.empty()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current_graph_fingerprint ^= hasher(types[0]) + 0x9e3779b97f4a7c15ULL + (current_graph_fingerprint << 6) + (current_graph_fingerprint >> 2); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+494
to
+511
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size_t current_graph_fingerprint = 1469598103934665603ULL; // FNV-1a seed-ish | |
| std::hash<std::string> hasher; | |
| // Incorporate topic count | |
| current_graph_fingerprint ^= all_topics.size() + 0x9e3779b97f4a7c15ULL + (current_graph_fingerprint << 6) + (current_graph_fingerprint >> 2); | |
| for (const auto & kv : all_topics) { | |
| const auto & topic = kv.first; | |
| const auto & types = kv.second; | |
| current_graph_fingerprint ^= hasher(topic) + 0x9e3779b97f4a7c15ULL + (current_graph_fingerprint << 6) + (current_graph_fingerprint >> 2); | |
| if (!types.empty()) { | |
| current_graph_fingerprint ^= hasher(types[0]) + 0x9e3779b97f4a7c15ULL + (current_graph_fingerprint << 6) + (current_graph_fingerprint >> 2); | |
| } | |
| } | |
| // Build a canonical string representation of the topic graph and hash it. | |
| // This avoids custom hash-combine logic and magic constants while still | |
| // providing a stable fingerprint for cache invalidation. | |
| std::ostringstream graph_repr; | |
| graph_repr << all_topics.size() << ';'; | |
| for (const auto & kv : all_topics) { | |
| const auto & topic = kv.first; | |
| const auto & types = kv.second; | |
| graph_repr << topic << '|'; | |
| if (!types.empty()) { | |
| graph_repr << types[0]; | |
| } | |
| graph_repr << ';'; | |
| } | |
| size_t current_graph_fingerprint = std::hash<std::string>{}(graph_repr.str()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Condition: topic_map_cache_.empty() - after the very first call, the cache will never be empty even if the graph truly has zero topics (empty map is still cached). Consider using std::optional or a bool flag cache_valid_ instead.
Copilot
AI
Feb 15, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cache invalidation check calls node_->get_topic_names_and_types() on every request to compute the fingerprint. This is a relatively expensive ROS 2 graph query that partially defeats the purpose of caching. While cheaper than building the full map, this still queries the ROS graph on every call.
Consider alternative approaches:
- Use a periodic refresh timer instead of on-demand checking
- Subscribe to graph events (though ROS 2 doesn't expose a simple callback mechanism)
- Accept that the cache may be slightly stale and refresh it periodically or on explicit invalidation calls
- At minimum, add performance benchmarking to verify this is actually faster than the uncached approach in realistic scenarios
| // Compute a lightweight fingerprint of the ROS graph (topics+types) | |
| // Note: ROS 2 does not expose a simple graph change counter; using | |
| // get_topic_names_and_types() and hashing the result is a reliable | |
| // way to detect graph changes that affect topic/component mapping. | |
| auto all_topics = node_->get_topic_names_and_types(); | |
| size_t current_graph_fingerprint = 1469598103934665603ULL; // FNV-1a seed-ish | |
| std::hash<std::string> hasher; | |
| // Incorporate topic count | |
| current_graph_fingerprint ^= all_topics.size() + 0x9e3779b97f4a7c15ULL + (current_graph_fingerprint << 6) + (current_graph_fingerprint >> 2); | |
| for (const auto & kv : all_topics) { | |
| const auto & topic = kv.first; | |
| const auto & types = kv.second; | |
| current_graph_fingerprint ^= hasher(topic) + 0x9e3779b97f4a7c15ULL + (current_graph_fingerprint << 6) + (current_graph_fingerprint >> 2); | |
| if (!types.empty()) { | |
| current_graph_fingerprint ^= hasher(types[0]) + 0x9e3779b97f4a7c15ULL + (current_graph_fingerprint << 6) + (current_graph_fingerprint >> 2); | |
| } | |
| } | |
| if (topic_map_cache_.empty() || cached_graph_change_count_ != current_graph_fingerprint) { | |
| topic_map_cache_ = build_component_topic_map(); | |
| cached_graph_change_count_ = current_graph_fingerprint; | |
| // Throttle expensive ROS graph queries: only recompute the fingerprint | |
| // if a minimum amount of time has elapsed since the last query. | |
| using Clock = std::chrono::steady_clock; | |
| static Clock::time_point last_graph_query_time{}; | |
| static size_t last_graph_fingerprint = 0U; | |
| constexpr auto kMin_graph_refresh_interval = std::chrono::milliseconds(500); | |
| const auto now = Clock::now(); | |
| // If we have a recent fingerprint and the cached graph change count matches, | |
| // return the cached map without re-querying the ROS graph. This accepts a | |
| // slightly stale cache in exchange for lower query overhead. | |
| if (!topic_map_cache_.empty() && | |
| cached_graph_change_count_ == last_graph_fingerprint && | |
| last_graph_query_time != Clock::time_point{} && | |
| (now - last_graph_query_time) < kMin_graph_refresh_interval) { | |
| // Return a copy to avoid callers holding a reference to internal cache | |
| return topic_map_cache_; | |
| } | |
| // Compute a lightweight fingerprint of the ROS graph (topics+types) | |
| // Note: ROS 2 does not expose a simple graph change counter; using | |
| // get_topic_names_and_types() and hashing the result is a reliable | |
| // way to detect graph changes that affect topic/component mapping. | |
| auto all_topics = node_->get_topic_names_and_types(); | |
| last_graph_query_time = now; | |
| size_t current_graph_fingerprint = 1469598103934665603ULL; // FNV-1a seed-ish | |
| std::hash<std::string> hasher; | |
| // Incorporate topic count | |
| current_graph_fingerprint ^= | |
| all_topics.size() + 0x9e3779b97f4a7c15ULL + (current_graph_fingerprint << 6) + (current_graph_fingerprint >> 2); | |
| for (const auto & kv : all_topics) { | |
| const auto & topic = kv.first; | |
| const auto & types = kv.second; | |
| current_graph_fingerprint ^= | |
| hasher(topic) + 0x9e3779b97f4a7c15ULL + (current_graph_fingerprint << 6) + (current_graph_fingerprint >> 2); | |
| if (!types.empty()) { | |
| current_graph_fingerprint ^= | |
| hasher(types[0]) + 0x9e3779b97f4a7c15ULL + (current_graph_fingerprint << 6) + (current_graph_fingerprint >> 2); | |
| } | |
| } | |
| if (topic_map_cache_.empty() || cached_graph_change_count_ != current_graph_fingerprint) { | |
| topic_map_cache_ = build_component_topic_map(); | |
| cached_graph_change_count_ = current_graph_fingerprint; | |
| last_graph_fingerprint = current_graph_fingerprint; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indentation broken:
is_system_topicuses 4-space indent, project convention is 2-space (Google clang-format). Please runclang-format.