Skip to content

Commit 4a422d8

Browse files
committed
Re-enable benchmark (#1245)
This PR refactors the ROS 2 benchmark suite to improve code quality, fix functionality issues, and provide comprehensive performance comparison data across rclcpp (C++), rclpy (Python), and rclnodejs (Node.js) client libraries. - Modernizes code with better error handling, proper shutdown procedures, and improved logging - Removes non-functional startup benchmarks and fixes bugs in service/topic stress tests - Adds comprehensive benchmark results and documentation showing performance characteristics Fix: #1244
1 parent 12548c7 commit 4a422d8

19 files changed

+719
-434
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ rclnodejs.init().then(() => {
2929
- [Electron-based Visualization](#electron-based-visualization)
3030
- [Using TypeScript](#using-rclnodejs-with-typescript)
3131
- [ROS2 Interface Message Generation](#ros2-interface-message-generation)
32+
- [Performance Benchmarks](#performance-benchmarks)
3233
- [Efficient Usage Tips](./docs/EFFICIENCY.md)
3334
- [FAQ and Known Issues](./docs/FAQ.md)
3435
- [Building from Scratch](./docs/BUILDING.md)
@@ -139,6 +140,18 @@ To generate messages from IDL files, use the `generate-messages-idl` npm script:
139140
npm run generate-messages-idl
140141
```
141142

143+
## Performance Benchmarks
144+
145+
Benchmark results for 1000 iterations with 1024KB messages (Ubuntu 24.04.3 WSL2, i7-1185G7):
146+
147+
| Library | Topic (ms) | Service (ms) |
148+
| ----------------------- | ---------- | ------------ |
149+
| **rclcpp (C++)** | 168 | 627 |
150+
| **rclnodejs (Node.js)** | 744 | 927 |
151+
| **rclpy (Python)** | 1,618 | 15,380 |
152+
153+
See [benchmark/README.md](benchmark/README.md) for details.
154+
142155
## Contributing
143156

144157
Please read the [Contributing Guide](./docs/CONTRIBUTING.md) before making a pull request.

benchmark/README.md

Lines changed: 118 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,137 @@
1-
# Benchmarks for ROS 2.0 clients
1+
# ROS 2 Client Library Benchmarks
22

3-
This folder contains code used to measure the performance between different ROS 2.0 clients.
3+
Performance benchmarks for comparing ROS 2 client libraries: C++ (rclcpp), Python (rclpy), and JavaScript (rclnodejs).
44

55
## Prerequisites
66

7-
1.Install ROS 2.0 from binary
7+
1. **ROS 2**: Install from [ros.org](https://docs.ros.org/en/jazzy/Installation.html)
8+
2. **Node.js**: v16+ for rclnodejs (from [nodejs.org](https://nodejs.org/))
9+
3. **rclnodejs**: Follow [installation guide](https://github.com/RobotWebTools/rclnodejs#installation)
10+
4. **Build Dependencies**: For C++ benchmarks: `sudo apt install libssl-dev cmake build-essential`
811

9-
You can download the latest binary package of ROS2 from [here](http://ci.ros2.org/view/packaging/) and follow the instructions to setup the environment.
12+
## Benchmark Structure
1013

11-
- [Linux](https://github.com/ros2/ros2/wiki/Linux-Install-Binary)
12-
- [macOS](https://github.com/ros2/ros2/wiki/OSX-Install-Binary)
13-
- [Windows](https://github.com/ros2/ros2/wiki/Windows-Install-Binary)
14+
Each client library has identical benchmark tests:
1415

15-
2.Install Node.js
16+
| Test Type | Description |
17+
| ----------- | ------------------------------------------- |
18+
| **topic** | Publisher/subscriber performance |
19+
| **service** | Client/service request-response performance |
1620

17-
Download the latest LTS edition from https://nodejs.org/en/
21+
## Performance Results
1822

19-
3.[Get the code](https://github.com/RobotWebTools/rclnodejs#get-code) and [install](https://github.com/RobotWebTools/rclnodejs#build-module)
23+
### Test Environment
2024

21-
## Benchmark directories
25+
**Hardware:**
2226

23-
The table lists the directories for each kind of the ROS 2.0 clients.
27+
- **CPU:** 11th Gen Intel(R) Core(TM) i7-1185G7 @ 3.00GHz (4 cores, 8 threads)
28+
- **Memory:** 8GB RAM
29+
- **Architecture:** x86_64
2430

25-
| Directory | Purpose |
26-
| :-------: | ------------------------------------------------------ |
27-
| topic | Benchmarks for `publisher` and `subscription` features |
28-
| service | Benchmarks for `client` and `service` features |
29-
| startup | Benchmarks for measuring the startup time consumption |
31+
**Software:**
3032

31-
## Run tests
33+
- **OS:** Ubuntu 24.04.3 LTS (WSL2)
34+
- **Kernel:** 6.6.87.2-microsoft-standard-WSL2
35+
- **ROS 2:** [Jazzy Patch Release 6](https://github.com/ros2/ros2/releases/tag/release-jazzy-20250820)
36+
- **C++ Compiler:** GCC 13.3.0 (Ubuntu 13.3.0-6ubuntu2~24.04)
37+
- **Python:** 3.12.3
38+
- **Node.js:** v22.18.0
39+
- **rclnodejs:** [v1.5.0](https://www.npmjs.com/package/rclnodejs/v/1.5.0)
3240

33-
1.Benchmark for [rclcpp](https://github.com/ros2/rclcpp)
41+
### Benchmark Results
3442

35-
- Compile the source files, `cd benchmark/rclcpp/` and run `colcon build`
36-
- The executable files locate at `build/rclcpp_benchmark/`
37-
- `your_benchmark -h` for help.
43+
Benchmark parameters: 1000 iterations, 1024KB message size
3844

39-
2.Benchmark for [rclpy](https://github.com/ros2/rclpy)
45+
| Client Library | Topic (ms) | Service (ms) | Performance Ratio |
46+
| ----------------------- | ---------- | ------------ | ------------------- |
47+
| **rclcpp (C++)** | 168 | 627 | Baseline (fastest) |
48+
| **rclpy (Python)** | 1,618 | 15,380 | 9.6x / 24.5x slower |
49+
| **rclnodejs (Node.js)** | 744 | 927 | 4.4x / 1.5x slower |
4050

41-
- Enter the Python scripts folder `benchmark/rclpy/`
42-
- `python3 your_benchmark -h` for help.
51+
_Last updated: August 30, 2025_
4352

44-
3.Benchmark for rclnodejs
53+
**Notes:**
4554

46-
- Enter the Node.js scripts folder `benchmark/rclnodejs/`
47-
- `node your_benchmark -h` for help.
55+
- Topic benchmarks: All libraries completed successfully with 1024KB messages
56+
- Service benchmarks: All libraries completed successfully with 1024KB responses
57+
- Performance ratios are relative to C++ baseline
58+
- C++ shows excellent performance as expected for a compiled language
59+
- Node.js performs significantly better than Python, likely due to V8 optimizations
60+
- Python service performance shows significant overhead with large payloads compared to topics
61+
62+
## Running Benchmarks
63+
64+
### C++ (rclcpp)
65+
66+
```bash
67+
cd benchmark/rclcpp/
68+
mkdir -p build && cd build
69+
source ~/Download/ros2-linux/local_setup.bash # Adjust path
70+
cmake .. && make
71+
# Run from build directory:
72+
./publisher-stress-test -r 1000 -s 1024
73+
./client-stress-test -r 1000
74+
```
75+
76+
### Python (rclpy)
77+
78+
```bash
79+
cd benchmark/rclpy/
80+
source ~/Download/ros2-linux/local_setup.bash # Adjust path
81+
python3 topic/publisher-stress-test.py -r 1000 -s 1024
82+
python3 service/client-stress-test.py -r 1000
83+
```
84+
85+
### JavaScript (rclnodejs)
86+
87+
```bash
88+
cd /path/to/rclnodejs/ # Project root
89+
source ~/Download/ros2-linux/local_setup.bash # Adjust path
90+
node benchmark/rclnodejs/topic/publisher-stress-test.js -r 1000 -s 1024
91+
node benchmark/rclnodejs/service/client-stress-test.js -r 1000
92+
```
93+
94+
## Test Workflow
95+
96+
For complete tests, run subscriber/service first, then publisher/client:
97+
98+
**Topic Test:**
99+
100+
```bash
101+
# Terminal 1: Start subscriber (adjust for your language)
102+
python3 topic/subscription-stress-test.py # Python
103+
./subscription-stress-test # C++ (from build dir)
104+
node benchmark/rclnodejs/topic/subscription-stress-test.js # Node.js
105+
106+
# Terminal 2: Run publisher benchmark
107+
python3 topic/publisher-stress-test.py -r 1000 -s 1024 # Python
108+
./publisher-stress-test -r 1000 -s 1024 # C++ (from build dir)
109+
node benchmark/rclnodejs/topic/publisher-stress-test.js -r 1000 -s 1024 # Node.js
110+
```
111+
112+
**Service Test:**
113+
114+
```bash
115+
# Terminal 1: Start service (adjust for your language)
116+
python3 service/service-stress-test.py -s 1024 # Python
117+
./service-stress-test -s 1024 # C++ (from build dir)
118+
node benchmark/rclnodejs/service/service-stress-test.js -s 1024 # Node.js
119+
120+
# Terminal 2: Run client benchmark
121+
python3 service/client-stress-test.py -r 1000 # Python
122+
./client-stress-test -r 1000 # C++ (from build dir)
123+
node benchmark/rclnodejs/service/client-stress-test.js -r 1000 # Node.js
124+
```
125+
126+
## Message Types
127+
128+
- **Topics**: `std_msgs/msg/UInt8MultiArray` (configurable size)
129+
- **Services**: `nav_msgs/srv/GetMap` (map data request/response)
130+
131+
## Notes
132+
133+
- All benchmarks use high-precision timing for accurate measurements
134+
- C++ provides baseline performance; Python/JavaScript show expected interpreted language overhead
135+
- All implementations are now working correctly across the three client libraries
136+
- Results vary by system configuration; use relative comparisons between client libraries
137+
- Service benchmarks involve request-response cycles with substantial data payloads (OccupancyGrid maps)

benchmark/rclcpp/CMakeLists.txt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
cmake_minimum_required(VERSION 3.5)
1+
cmake_minimum_required(VERSION 3.8)
22

33
project(rclcpp_benchmark)
44

5-
# Default to C++14
5+
# Default to C++17
66
if(NOT CMAKE_CXX_STANDARD)
7-
set(CMAKE_CXX_STANDARD 14)
7+
set(CMAKE_CXX_STANDARD 17)
8+
endif()
9+
10+
if(CMAKE_CXX_STANDARD LESS 17)
11+
set(CMAKE_CXX_STANDARD 17)
812
endif()
913

1014
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
@@ -39,6 +43,5 @@ custom_executable(publisher-stress-test topic/publisher-stress-test.cpp)
3943
custom_executable(subscription-stress-test topic/subscription-stress-test.cpp)
4044
custom_executable(client-stress-test service/client-stress-test.cpp)
4145
custom_executable(service-stress-test service/service-stress-test.cpp)
42-
custom_executable(startup-test startup/startup-test.cpp)
4346

4447
ament_package()

benchmark/rclcpp/package.xml

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
<?xml version="1.0"?>
2-
<?xml-model href="http://download.ros.org/schema/package_format2.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
3-
<package format="2">
2+
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
3+
<package format="3">
44
<name>rclcpp_benchmark</name>
55
<version>0.0.1</version>
66
<description>Benchmark for rclcpp</description>
77
<maintainer email="[email protected]">Minggang Wang</maintainer>
88
<license>Apache License 2.0</license>
9+
910
<buildtool_depend>ament_cmake</buildtool_depend>
10-
<build_depend>rclcpp</build_depend>
11-
<exec_depend>rclcpp</exec_depend>
11+
12+
<depend>rclcpp</depend>
13+
<depend>example_interfaces</depend>
14+
<depend>sensor_msgs</depend>
15+
<depend>std_msgs</depend>
16+
<depend>std_srvs</depend>
17+
<depend>nav_msgs</depend>
18+
19+
<test_depend>ament_lint_auto</test_depend>
20+
<test_depend>ament_lint_common</test_depend>
21+
1222
<export>
1323
<build_type>ament_cmake</build_type>
1424
</export>

benchmark/rclcpp/service/client-stress-test.cpp

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@
2222
#include "utilities.hpp"
2323

2424
void ShowUsage(const std::string name) {
25-
std::cerr << "Usage: " << name << " [options]\n"
26-
<< "\nOptions:\n"
27-
<< "\n--run <n> \tHow many times to run\n"
28-
<< "--help \toutput usage information"
29-
<< std::endl;
25+
std::cerr << "Usage: " << name << " [options]\n"
26+
<< "\nOptions:\n"
27+
<< "\n-r, --run <n> \tHow many times to run\n"
28+
<< "-h, --help \toutput usage information" << std::endl;
3029
}
3130

3231
int main(int argc, char* argv[]) {
@@ -36,34 +35,54 @@ int main(int argc, char* argv[]) {
3635
for (int i = 1; i < argc; i++) {
3736
std::string arg = argv[i];
3837
if ((arg == "-h") || (arg == "--help")) {
39-
ShowUsage(argv[0]);
40-
return 0;
38+
ShowUsage(argv[0]);
39+
return 0;
4140
} else if (arg.find("--run=") != std::string::npos) {
42-
total_times = std::stoi(arg.substr(arg.find("=") + 1));
41+
total_times = std::stoi(arg.substr(arg.find("=") + 1));
42+
} else if (arg == "-r" && i + 1 < argc) {
43+
total_times = std::stoi(argv[++i]);
4344
}
4445
}
4546
printf(
4647
"The client will send a GetMap request continuously until receiving "
47-
"response %d times.\n", total_times);
48+
"response %d times.\n",
49+
total_times);
4850

4951
auto start = std::chrono::high_resolution_clock::now();
5052
auto node = rclcpp::Node::make_shared("stress_client_rclcpp");
5153
auto client = node->create_client<nav_msgs::srv::GetMap>("get_map");
5254
auto request = std::make_shared<nav_msgs::srv::GetMap::Request>();
5355
auto received_times = 0;
5456

57+
// Wait for service to be available
58+
while (!client->wait_for_service(std::chrono::seconds(1))) {
59+
if (!rclcpp::ok()) {
60+
RCLCPP_ERROR(rclcpp::get_logger("rclcpp"),
61+
"Interrupted while waiting for the service. Exiting.");
62+
return 0;
63+
}
64+
RCLCPP_INFO(rclcpp::get_logger("rclcpp"),
65+
"Service not available, waiting again...");
66+
}
67+
5568
while (rclcpp::ok()) {
56-
if (received_times > total_times) {
69+
if (received_times >= total_times) {
5770
rclcpp::shutdown();
5871
auto end = std::chrono::high_resolution_clock::now();
5972
LogTimeConsumption(start, end);
73+
break;
6074
} else {
6175
auto result_future = client->async_send_request(request);
62-
if (rclcpp::spin_until_future_complete(node, result_future) !=
63-
rclcpp::executor::FutureReturnCode::SUCCESS) {
64-
RCLCPP_ERROR(node->get_logger(), "service call failed.");
76+
77+
// Spin until future is ready using the node directly
78+
auto future_status = rclcpp::spin_until_future_complete(
79+
node, result_future, std::chrono::seconds(10));
80+
81+
if (future_status != rclcpp::FutureReturnCode::SUCCESS) {
82+
RCLCPP_ERROR(node->get_logger(), "Service call failed or timed out.");
6583
return 1;
6684
}
85+
6786
auto result = result_future.get();
6887
received_times++;
6988
}

benchmark/rclcpp/service/service-stress-test.cpp

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,10 @@
2121
#include "rclcpp/rclcpp.hpp"
2222

2323
void ShowUsage(const std::string name) {
24-
std::cerr << "Usage: " << name << " [options]\n"
25-
<< "\nOptions:\n"
26-
<< "\n--size [size_kb]\tThe block size\n"
27-
<< "--help \toutput usage information"
28-
<< std::endl;
24+
std::cerr << "Usage: " << name << " [options]\n"
25+
<< "\nOptions:\n"
26+
<< "\n-s, --size [size_kb]\tThe block size\n"
27+
<< "-h, --help \toutput usage information" << std::endl;
2928
}
3029

3130
int main(int argc, char* argv[]) {
@@ -35,26 +34,24 @@ int main(int argc, char* argv[]) {
3534
for (int i = 1; i < argc; i++) {
3635
std::string arg = argv[i];
3736
if ((arg == "-h") || (arg == "--help")) {
38-
ShowUsage(argv[0]);
39-
return 0;
37+
ShowUsage(argv[0]);
38+
return 0;
4039
} else if (arg.find("--size=") != std::string::npos) {
41-
amount = std::stoi(arg.substr(arg.find("=") + 1));
40+
amount = std::stoi(arg.substr(arg.find("=") + 1));
41+
} else if (arg == "-s" && i + 1 < argc) {
42+
amount = std::stoi(argv[++i]);
4243
}
4344
}
4445

4546
auto node = rclcpp::Node::make_shared("stress_service_rclcpp");
46-
auto sub = node->create_service<nav_msgs::srv::GetMap>(
47+
auto service = node->create_service<nav_msgs::srv::GetMap>(
4748
"get_map",
48-
[amount](const std::shared_ptr<rmw_request_id_t> request_header,
49-
const std::shared_ptr<nav_msgs::srv::GetMap::Request> request,
50-
const std::shared_ptr<nav_msgs::srv::GetMap::Response> response) {
51-
(void)request_header;
49+
[amount](const std::shared_ptr<nav_msgs::srv::GetMap::Request> request,
50+
std::shared_ptr<nav_msgs::srv::GetMap::Response> response) {
5251
(void)request;
53-
response->map.header.stamp.sec = 123456;
54-
response->map.header.stamp.nanosec = 789;
52+
response->map.header.stamp = rclcpp::Clock().now();
5553
response->map.header.frame_id = "main_frame";
56-
response->map.info.map_load_time.sec = 123456;
57-
response->map.info.map_load_time.nanosec = 789;
54+
response->map.info.map_load_time = rclcpp::Clock().now();
5855
response->map.info.resolution = 1.0;
5956
response->map.info.width = 1024;
6057
response->map.info.height = 768;
@@ -64,7 +61,7 @@ int main(int argc, char* argv[]) {
6461
response->map.info.origin.orientation.x = 0.0;
6562
response->map.info.origin.orientation.y = 0.0;
6663
response->map.info.origin.orientation.z = 0.0;
67-
response->map.info.origin.orientation.w = 0.0;
64+
response->map.info.origin.orientation.w = 1.0;
6865
response->map.data = std::vector<int8_t>(1024 * amount, 125);
6966
});
7067
rclcpp::spin(node);

0 commit comments

Comments
 (0)