Skip to content

Commit a98fc0b

Browse files
committed
core: speed up deserialization using std::to_chars
When parsing messages, profiling shows that most time is spent in ostream to format the values to string. This adds a test and a faster implementation based on std::to_chars.
1 parent 02df9d5 commit a98fc0b

File tree

4 files changed

+526
-73
lines changed

4 files changed

+526
-73
lines changed

src/mavsdk/core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,7 @@ list(APPEND UNIT_TEST_SOURCES
229229
${PROJECT_SOURCE_DIR}/mavsdk/core/file_cache_test.cpp
230230
${PROJECT_SOURCE_DIR}/mavsdk/core/locked_queue_test.cpp
231231
${PROJECT_SOURCE_DIR}/mavsdk/core/geometry_test.cpp
232+
${PROJECT_SOURCE_DIR}/mavsdk/core/libmav_receiver_test.cpp
232233
${PROJECT_SOURCE_DIR}/mavsdk/core/math_utils_test.cpp
233234
${PROJECT_SOURCE_DIR}/mavsdk/core/mavsdk_test.cpp
234235
${PROJECT_SOURCE_DIR}/mavsdk/core/mavsdk_time_test.cpp

src/mavsdk/core/libmav_receiver.cpp

Lines changed: 177 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,186 @@
66
#include <variant>
77
#include <cstring>
88
#include <sstream>
9+
#include <charconv>
910
#include <cmath>
1011
#include "log.h"
1112

1213
namespace mavsdk {
1314

15+
// Original implementation using std::ostream << for float/double conversion.
16+
// Kept for benchmark comparison.
17+
template<typename T> void value_to_json_stream(std::ostream& json_stream, const T& value)
18+
{
19+
if constexpr (
20+
std::is_same_v<T, uint8_t> || std::is_same_v<T, uint16_t> || std::is_same_v<T, uint32_t> ||
21+
std::is_same_v<T, uint64_t> || std::is_same_v<T, int8_t> || std::is_same_v<T, int16_t> ||
22+
std::is_same_v<T, int32_t> || std::is_same_v<T, int64_t>) {
23+
json_stream << static_cast<int64_t>(value);
24+
} else if constexpr (std::is_same_v<T, char>) {
25+
json_stream << static_cast<int>(value);
26+
} else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
27+
if (!std::isfinite(value)) {
28+
json_stream << "null";
29+
} else {
30+
json_stream << value;
31+
}
32+
} else if constexpr (std::is_same_v<T, std::string>) {
33+
json_stream << "\"" << value << "\"";
34+
} else if constexpr (
35+
std::is_same_v<T, std::vector<uint8_t>> || std::is_same_v<T, std::vector<int8_t>>) {
36+
// Handle uint8_t/int8_t vectors specially to avoid character output
37+
json_stream << "[";
38+
bool first = true;
39+
for (const auto& elem : value) {
40+
if (!first)
41+
json_stream << ",";
42+
first = false;
43+
json_stream << static_cast<int>(elem);
44+
}
45+
json_stream << "]";
46+
} else if constexpr (
47+
std::is_same_v<T, std::vector<uint16_t>> || std::is_same_v<T, std::vector<uint32_t>> ||
48+
std::is_same_v<T, std::vector<uint64_t>> || std::is_same_v<T, std::vector<int16_t>> ||
49+
std::is_same_v<T, std::vector<int32_t>> || std::is_same_v<T, std::vector<int64_t>>) {
50+
// Handle integer vector types
51+
json_stream << "[";
52+
bool first = true;
53+
for (const auto& elem : value) {
54+
if (!first)
55+
json_stream << ",";
56+
first = false;
57+
json_stream << elem;
58+
}
59+
json_stream << "]";
60+
} else if constexpr (
61+
std::is_same_v<T, std::vector<float>> || std::is_same_v<T, std::vector<double>>) {
62+
// Handle float/double vector types with NaN check
63+
json_stream << "[";
64+
bool first = true;
65+
for (const auto& elem : value) {
66+
if (!first)
67+
json_stream << ",";
68+
first = false;
69+
if (!std::isfinite(elem)) {
70+
json_stream << "null";
71+
} else {
72+
json_stream << elem;
73+
}
74+
}
75+
json_stream << "]";
76+
} else {
77+
// Fallback for unknown types
78+
json_stream << "null";
79+
}
80+
}
81+
82+
// Optimized implementation using std::to_chars for float/double conversion.
83+
template<typename T> void value_to_json_stream_fast(std::ostream& json_stream, const T& value)
84+
{
85+
if constexpr (
86+
std::is_same_v<T, uint8_t> || std::is_same_v<T, uint16_t> || std::is_same_v<T, uint32_t> ||
87+
std::is_same_v<T, uint64_t> || std::is_same_v<T, int8_t> || std::is_same_v<T, int16_t> ||
88+
std::is_same_v<T, int32_t> || std::is_same_v<T, int64_t>) {
89+
char buf[32];
90+
auto [ptr, ec] = std::to_chars(buf, buf + sizeof(buf), value);
91+
json_stream.write(buf, ptr - buf);
92+
} else if constexpr (std::is_same_v<T, char>) {
93+
char buf[8];
94+
auto [ptr, ec] = std::to_chars(buf, buf + sizeof(buf), static_cast<int>(value));
95+
json_stream.write(buf, ptr - buf);
96+
} else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, double>) {
97+
if (!std::isfinite(value)) {
98+
json_stream << "null";
99+
} else {
100+
char buf[32];
101+
auto [ptr, ec] = std::to_chars(buf, buf + sizeof(buf), value);
102+
json_stream.write(buf, ptr - buf);
103+
}
104+
} else if constexpr (std::is_same_v<T, std::string>) {
105+
json_stream << "\"" << value << "\"";
106+
} else if constexpr (
107+
std::is_same_v<T, std::vector<uint8_t>> || std::is_same_v<T, std::vector<int8_t>>) {
108+
json_stream << "[";
109+
bool first = true;
110+
for (const auto& elem : value) {
111+
if (!first)
112+
json_stream << ",";
113+
first = false;
114+
char buf[8];
115+
auto [ptr, ec] = std::to_chars(buf, buf + sizeof(buf), static_cast<int>(elem));
116+
json_stream.write(buf, ptr - buf);
117+
}
118+
json_stream << "]";
119+
} else if constexpr (
120+
std::is_same_v<T, std::vector<uint16_t>> || std::is_same_v<T, std::vector<uint32_t>> ||
121+
std::is_same_v<T, std::vector<uint64_t>> || std::is_same_v<T, std::vector<int16_t>> ||
122+
std::is_same_v<T, std::vector<int32_t>> || std::is_same_v<T, std::vector<int64_t>>) {
123+
json_stream << "[";
124+
bool first = true;
125+
for (const auto& elem : value) {
126+
if (!first)
127+
json_stream << ",";
128+
first = false;
129+
char buf[32];
130+
auto [ptr, ec] = std::to_chars(buf, buf + sizeof(buf), elem);
131+
json_stream.write(buf, ptr - buf);
132+
}
133+
json_stream << "]";
134+
} else if constexpr (
135+
std::is_same_v<T, std::vector<float>> || std::is_same_v<T, std::vector<double>>) {
136+
json_stream << "[";
137+
bool first = true;
138+
for (const auto& elem : value) {
139+
if (!first)
140+
json_stream << ",";
141+
first = false;
142+
if (!std::isfinite(elem)) {
143+
json_stream << "null";
144+
} else {
145+
char buf[32];
146+
auto [ptr, ec] = std::to_chars(buf, buf + sizeof(buf), elem);
147+
json_stream.write(buf, ptr - buf);
148+
}
149+
}
150+
json_stream << "]";
151+
} else {
152+
// Fallback for unknown types
153+
json_stream << "null";
154+
}
155+
}
156+
157+
// Explicit instantiations for benchmark testing - original
158+
template void value_to_json_stream<float>(std::ostream&, const float&);
159+
template void value_to_json_stream<double>(std::ostream&, const double&);
160+
template void value_to_json_stream<int8_t>(std::ostream&, const int8_t&);
161+
template void value_to_json_stream<int16_t>(std::ostream&, const int16_t&);
162+
template void value_to_json_stream<int32_t>(std::ostream&, const int32_t&);
163+
template void value_to_json_stream<int64_t>(std::ostream&, const int64_t&);
164+
template void value_to_json_stream<uint8_t>(std::ostream&, const uint8_t&);
165+
template void value_to_json_stream<uint16_t>(std::ostream&, const uint16_t&);
166+
template void value_to_json_stream<uint32_t>(std::ostream&, const uint32_t&);
167+
template void value_to_json_stream<uint64_t>(std::ostream&, const uint64_t&);
168+
template void value_to_json_stream<std::string>(std::ostream&, const std::string&);
169+
template void value_to_json_stream<std::vector<float>>(std::ostream&, const std::vector<float>&);
170+
template void value_to_json_stream<std::vector<double>>(std::ostream&, const std::vector<double>&);
171+
172+
// Explicit instantiations for benchmark testing - optimized
173+
template void value_to_json_stream_fast<float>(std::ostream&, const float&);
174+
template void value_to_json_stream_fast<double>(std::ostream&, const double&);
175+
template void value_to_json_stream_fast<int8_t>(std::ostream&, const int8_t&);
176+
template void value_to_json_stream_fast<int16_t>(std::ostream&, const int16_t&);
177+
template void value_to_json_stream_fast<int32_t>(std::ostream&, const int32_t&);
178+
template void value_to_json_stream_fast<int64_t>(std::ostream&, const int64_t&);
179+
template void value_to_json_stream_fast<uint8_t>(std::ostream&, const uint8_t&);
180+
template void value_to_json_stream_fast<uint16_t>(std::ostream&, const uint16_t&);
181+
template void value_to_json_stream_fast<uint32_t>(std::ostream&, const uint32_t&);
182+
template void value_to_json_stream_fast<uint64_t>(std::ostream&, const uint64_t&);
183+
template void value_to_json_stream_fast<std::string>(std::ostream&, const std::string&);
184+
template void
185+
value_to_json_stream_fast<std::vector<float>>(std::ostream&, const std::vector<float>&);
186+
template void
187+
value_to_json_stream_fast<std::vector<double>>(std::ostream&, const std::vector<double>&);
188+
14189
LibmavReceiver::LibmavReceiver(MavsdkImpl& mavsdk_impl) : _mavsdk_impl(mavsdk_impl)
15190
{
16191
// No need for individual BufferParser - we'll use MavsdkImpl's thread-safe parsing
@@ -116,80 +291,9 @@ std::string LibmavReceiver::libmav_message_to_json(const mav::Message& msg) cons
116291
if (variant_opt) {
117292
const auto& variant = variant_opt.value();
118293

119-
// Convert variant to JSON string based on the field type
294+
// Convert variant to JSON string using extracted function
120295
std::visit(
121-
[&json_stream](const auto& value) {
122-
using T = std::decay_t<decltype(value)>;
123-
124-
if constexpr (
125-
std::is_same_v<T, uint8_t> || std::is_same_v<T, uint16_t> ||
126-
std::is_same_v<T, uint32_t> || std::is_same_v<T, uint64_t> ||
127-
std::is_same_v<T, int8_t> || std::is_same_v<T, int16_t> ||
128-
std::is_same_v<T, int32_t> || std::is_same_v<T, int64_t>) {
129-
json_stream << static_cast<int64_t>(value);
130-
} else if constexpr (std::is_same_v<T, char>) {
131-
json_stream << static_cast<int>(value);
132-
} else if constexpr (
133-
std::is_same_v<T, float> || std::is_same_v<T, double>) {
134-
if (!std::isfinite(value)) {
135-
json_stream << "null";
136-
} else {
137-
json_stream << value;
138-
}
139-
} else if constexpr (std::is_same_v<T, std::string>) {
140-
json_stream << "\"" << value << "\"";
141-
} else if constexpr (
142-
std::is_same_v<T, std::vector<uint8_t>> ||
143-
std::is_same_v<T, std::vector<int8_t>>) {
144-
// Handle uint8_t/int8_t vectors specially to avoid character output
145-
json_stream << "[";
146-
bool first = true;
147-
for (const auto& elem : value) {
148-
if (!first)
149-
json_stream << ",";
150-
first = false;
151-
json_stream << static_cast<int>(elem);
152-
}
153-
json_stream << "]";
154-
} else if constexpr (
155-
std::is_same_v<T, std::vector<uint16_t>> ||
156-
std::is_same_v<T, std::vector<uint32_t>> ||
157-
std::is_same_v<T, std::vector<uint64_t>> ||
158-
std::is_same_v<T, std::vector<int16_t>> ||
159-
std::is_same_v<T, std::vector<int32_t>> ||
160-
std::is_same_v<T, std::vector<int64_t>>) {
161-
// Handle integer vector types
162-
json_stream << "[";
163-
bool first = true;
164-
for (const auto& elem : value) {
165-
if (!first)
166-
json_stream << ",";
167-
first = false;
168-
json_stream << elem;
169-
}
170-
json_stream << "]";
171-
} else if constexpr (
172-
std::is_same_v<T, std::vector<float>> ||
173-
std::is_same_v<T, std::vector<double>>) {
174-
// Handle float/double vector types with NaN check
175-
json_stream << "[";
176-
bool first = true;
177-
for (const auto& elem : value) {
178-
if (!first)
179-
json_stream << ",";
180-
first = false;
181-
if (!std::isfinite(elem)) {
182-
json_stream << "null";
183-
} else {
184-
json_stream << elem;
185-
}
186-
}
187-
json_stream << "]";
188-
} else {
189-
// Fallback for unknown types
190-
json_stream << "null";
191-
}
192-
},
296+
[&json_stream](const auto& value) { value_to_json_stream(json_stream, value); },
193297
variant);
194298
} else {
195299
// Field not present or failed to extract

src/mavsdk/core/libmav_receiver.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
#include <cstdint>
66
#include <optional>
77
#include <memory>
8+
#include <ostream>
9+
#include <vector>
810
#include "mavlink_include.h"
911
#include "mavsdk.h"
1012

@@ -23,6 +25,32 @@ namespace mavsdk {
2325
// Forward declaration for thread-safe MessageSet operations
2426
class MavsdkImpl;
2527

28+
// Original implementation using std::ostream << for float/double conversion.
29+
template<typename T> void value_to_json_stream(std::ostream& json_stream, const T& value);
30+
31+
// Optimized implementation using std::to_chars for float/double conversion.
32+
template<typename T> void value_to_json_stream_fast(std::ostream& json_stream, const T& value);
33+
34+
// Explicit template declarations - original
35+
extern template void value_to_json_stream<float>(std::ostream&, const float&);
36+
extern template void value_to_json_stream<double>(std::ostream&, const double&);
37+
extern template void value_to_json_stream<int32_t>(std::ostream&, const int32_t&);
38+
extern template void value_to_json_stream<uint32_t>(std::ostream&, const uint32_t&);
39+
extern template void
40+
value_to_json_stream<std::vector<float>>(std::ostream&, const std::vector<float>&);
41+
extern template void
42+
value_to_json_stream<std::vector<double>>(std::ostream&, const std::vector<double>&);
43+
44+
// Explicit template declarations - optimized
45+
extern template void value_to_json_stream_fast<float>(std::ostream&, const float&);
46+
extern template void value_to_json_stream_fast<double>(std::ostream&, const double&);
47+
extern template void value_to_json_stream_fast<int32_t>(std::ostream&, const int32_t&);
48+
extern template void value_to_json_stream_fast<uint32_t>(std::ostream&, const uint32_t&);
49+
extern template void
50+
value_to_json_stream_fast<std::vector<float>>(std::ostream&, const std::vector<float>&);
51+
extern template void
52+
value_to_json_stream_fast<std::vector<double>>(std::ostream&, const std::vector<double>&);
53+
2654
class LibmavReceiver {
2755
public:
2856
explicit LibmavReceiver(MavsdkImpl& mavsdk_impl);

0 commit comments

Comments
 (0)