improve MessageTypeSupport performance. - #562
Conversation
Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
|
@biodranik CCed |
|
Pulls: #562 |
|
@ahcorde is this good to go? |
|
I would like to see a performance measurement first. |
|
@jmachowinski do you have benchmark test? or anybody else does? |
|
Nope, and I see this is a problem, because we have no way of figuring out, if we improve stuff, or introduce regressions. @mjcarroll recently tried to improve things, and afterwards we figured out, the benchmark was pointless. |
biodranik
left a comment
There was a problem hiding this comment.
This also looks like it will not improve the real performance of a system, as this constructor will only be called a few times during initialization of the serializers etc. And this is exactly my point, I think we are optimizing the wrong things here...
Even if this is not a performance-critical place, avoiding unnecessary, avoidable overhead everywhere in the code would be a good example to new contributors (and LLMs) to produce a better quality, faster code.
FOSS should not be slow and ugly, right?
I agree that this isn't going to have a huge impact, but getting this code cleaner won't hurt. I wouldn't encourage people to go out of their way to hunt for these things, but in this case it was uncovered when looking for other optimizations, so it seems low cost to just fix now. |
|
Pulls: #562 |
Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com>
|
we can see the improvement, tomoyafujita@~/DVT/02_TEST >uname -srm
Linux 6.17.0-14-generic x86_64
tomoyafujita@~/DVT/02_TEST >grep 'model name' /proc/cpuinfo | head -1
model name : Intel(R) Core(TM) i7-14700KF
tomoyafujita@~/DVT/02_TEST >g++ --version | head -1
g++ (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
tomoyafujita@~/DVT/02_TEST >./bench
Correctness check passed.
=== MessageTypeSupport getName() benchmark ===
Iterations: 1000000 x 7 types = 7000000 calls each
OLD (regex + ostringstream): 3221.22 ms
NEW (find/replace + +=): 368.914 ms
OLD per call: 460.175 ns
NEW per call: 52.702 ns
Speedup: 8.73164x
with the following simulated benchmark program. // bench_message_type_support.cpp
// Simulates the old vs new MessageTypeSupport constructor
// from ros2/rmw_cyclonedds PR #562 (commit 8e0ed9d)
//
// Build & run:
// g++ -O2 -o bench bench_message_type_support.cpp && time ./bench
#include <iostream>
#include <string>
#include <regex>
#include <sstream>
#include <chrono>
#include <vector>
#include <cstring>
// Prevent compiler from optimizing away the result
static volatile size_t sink = 0;
// ============================================================
// OLD: std::regex_replace + std::ostringstream
// (MessageTypeSupport_impl.hpp before PR #562)
// ============================================================
std::string getName_old(const char* message_namespace_, const char* message_name_) {
std::ostringstream ss;
std::string message_namespace(message_namespace_);
std::string message_name(message_name_);
if (!message_namespace.empty()) {
// Find and replace C namespace separator with C++
message_namespace = std::regex_replace(message_namespace, std::regex("__"), "::");
ss << message_namespace << "::";
}
ss << "dds_::" << message_name << "_";
return ss.str();
}
// ============================================================
// NEW: null check + find/replace loop + reserve + direct +=
// (MessageTypeSupport_impl.hpp after PR #562, commit 8e0ed9d)
// ============================================================
std::string getName_new(const char* message_namespace_, const char* message_name_) {
std::string message_name(message_name_);
std::string name;
if (message_namespace_ != nullptr && message_namespace_[0] != '\0') {
std::string message_namespace(message_namespace_);
// Find and replace C namespace separator with C++
std::string::size_type pos = 0;
while ((pos = message_namespace.find("__", pos)) != std::string::npos) {
message_namespace.replace(pos, 2, "::");
pos += 2;
}
name.reserve(
message_namespace.size() + 2 + 5 + message_name.size() + 1);
name += message_namespace;
name += "::";
} else {
name.reserve(5 + message_name.size() + 1);
}
name += "dds_::";
name += message_name;
name += '_';
return name;
}
int main() {
// Realistic ROS 2 type namespaces (C typesupport uses "__" separator)
std::vector<std::pair<const char*, const char*>> test_cases = {
{"sensor_msgs__msg", "PointCloud2"},
{"geometry_msgs__msg", "PoseStamped"},
{"nav_msgs__msg", "OccupancyGrid"},
{"tf2_msgs__msg", "TFMessage"},
{"", "String"}, // empty namespace
{nullptr, "Header"}, // null namespace
{"very__deep__nested__ns__msg", "Custom"},
};
const int ITERATIONS = 1'000'000;
// Verify both produce same output
for (auto& [ns, name] : test_cases) {
const char* safe_ns = ns ? ns : "";
std::string old_result = getName_old(safe_ns, name);
std::string new_result = getName_new(ns, name);
if (old_result != new_result) {
std::cerr << "MISMATCH for ns=\"" << safe_ns << "\" name=\"" << name << "\"\n"
<< " old: " << old_result << "\n"
<< " new: " << new_result << "\n";
return 1;
}
}
std::cout << "Correctness check passed.\n\n";
// --- Benchmark OLD ---
auto t0 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERATIONS; i++) {
for (auto& [ns, name] : test_cases) {
const char* safe_ns = ns ? ns : "";
sink += getName_old(safe_ns, name).size();
}
}
auto t1 = std::chrono::high_resolution_clock::now();
// --- Benchmark NEW ---
auto t2 = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERATIONS; i++) {
for (auto& [ns, name] : test_cases) {
sink += getName_new(ns, name).size();
}
}
auto t3 = std::chrono::high_resolution_clock::now();
double old_ms = std::chrono::duration<double, std::milli>(t1 - t0).count();
double new_ms = std::chrono::duration<double, std::milli>(t3 - t2).count();
long total_calls = (long)ITERATIONS * test_cases.size();
std::cout << "=== MessageTypeSupport getName() benchmark ===\n";
std::cout << "Iterations: " << ITERATIONS << " x " << test_cases.size()
<< " types = " << total_calls << " calls each\n\n";
std::cout << "OLD (regex + ostringstream): " << old_ms << " ms\n";
std::cout << "NEW (find/replace + +=): " << new_ms << " ms\n\n";
std::cout << "OLD per call: " << (old_ms / total_calls) * 1e6 << " ns\n";
std::cout << "NEW per call: " << (new_ms / total_calls) * 1e6 << " ns\n\n";
std::cout << "Speedup: " << old_ms / new_ms << "x\n";
return 0;
}i guess this is worth to take??? @mjcarroll @jmachowinski @Timple @biodranik wdyt? |
|
Almost 10x faster, why not :) |
|
@fujitatomoya The CI run did not use cyclone as default DDS, but I think we are good to merge anyway. |
|
Windows unrelated, merging. |
|
@Mergifyio backport kilted jazzy humble |
✅ Backports have been createdDetails
|
* improve MessageTypeSupport performance. Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> * address review comments. Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> --------- Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> (cherry picked from commit fd48d58)
* improve MessageTypeSupport performance. Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> * address review comments. Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> --------- Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> (cherry picked from commit fd48d58)
* improve MessageTypeSupport performance. Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> * address review comments. Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> --------- Signed-off-by: Tomoya Fujita <Tomoya.Fujita@sony.com> (cherry picked from commit fd48d58)
Description
The optimization follows biodranik's suggestion from the PR review, addressing the performance issue at its source.
Removed expensive operations:
Replaced with efficient plain string operations:
related to #561
Is this user-facing behavior change?
No,
Did you use Generative AI?
Yes, Claude Sonnet 4.6
Additional Information