Skip to content

Commit 74b43aa

Browse files
authored
perf(generator): use memcpy paths for primitive sequence conversion (#26)
For UnboundedSequence<BasicType> fields, route both conversion directions through single-memcpy paths instead of element-by-element loops. - from_rmw_message: emit msg.field.into() instead of msg.field.into_iter().collect(). Routes through rosidl_runtime_rs' Copy-bounded From<Sequence<T>> for Vec<T> -> as_slice().to_vec() -> memcpy. The old path paid a per-element read + zero-write through SequenceIterator::next(). - into_rmw_message Owned arm: emit msg.field.as_slice().into() instead of msg.field.into(). Routes through From<&[T]> for Sequence<T> -> Sequence::new + clone_from_slice -> memcpy (stdlib specializes clone_from_slice for T: Copy). The old path went through From<Vec<T>> -> from_iter -> extend, paying a per-element IndexMut write. The Borrowed arm of into_rmw_message already used as_slice().into() and is unchanged. Non-primitive element types (String, WString, nested messages) still use .into_iter().map(...).collect() -- they need real per-element conversion regardless. Also restructures the from_rmw_message UnboundedSequence branch so each element-type arm emits a single complete expression rather than sharing a chained .into_iter()...collect() across arms. Microbenchmarks (Sequence<u8> 65536 <-> Vec<u8>): receive: 23.3 us -> 513 ns (45.4x) publish: 28.3 us -> 726 ns (39.0x) End-to-end (UInt8MultiArray, single-process, reliable QoS, rmw_fastrtps_cpp, 64 KiB, median of 3 x 5s): rclrs (main): 30,578 msg/s rclrs (this branch): 135,560 msg/s (4.4x over main, ~90% of rclcpp) rclcpp baseline: 151,279 msg/s rclrs + Worker sub: 224,576 msg/s (~1.48x of rclcpp single-process) Requires rosidl_runtime_rs with the new Copy-bounded From<Sequence<T>> for Vec<T> impl (companion PR). Refs ros2-rust/ros2_rust#628 improvement (1).
1 parent 3cd60da commit 74b43aa

1 file changed

Lines changed: 19 additions & 3 deletions

File tree

rosidl_generator_rs/resource/templates/msg_idiomatic.rs.em

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,12 @@ impl rosidl_runtime_rs::Message for @(type_name) {
139139
.into_iter()
140140
.map(|elem| @(get_rs_type(member.type.value_type))::into_rmw_message(std::borrow::Cow::Owned(elem)).into_owned())
141141
.collect(),
142+
@# Owned BasicType sequences: borrow the slice and route through
143+
@# From<&[T]> for Sequence<T> so the copy compiles to a memcpy via
144+
@# clone_from_slice's Copy specialization, instead of From<Vec<T>>'s
145+
@# per-element extend path. See ros2-rust/ros2_rust#628.
142146
@[ else]@
143-
@(get_rs_name(member.name)): msg.@(get_rs_name(member.name)).into(),
147+
@(get_rs_name(member.name)): msg.@(get_rs_name(member.name)).as_slice().into(),
144148
@[ end if]@
145149
@#
146150
@#
@@ -243,14 +247,26 @@ impl rosidl_runtime_rs::Message for @(type_name) {
243247
@#
244248
@# == UnboundedSequence ==
245249
@[ elif isinstance(member.type, UnboundedSequence)]@
250+
@# Primitive sequences use the From<Sequence<T>> for Vec<T> impl (memcpy via
251+
@# as_slice().to_vec()), avoiding O(n) per-element read/zero-write that the
252+
@# SequenceIterator path performs. See ros2-rust/ros2_rust#628.
253+
@[ if isinstance(member.type.value_type, BasicType)]@
254+
@(get_rs_name(member.name)): msg.@(get_rs_name(member.name)).into(),
255+
@[ elif isinstance(member.type.value_type, UnboundedString) or isinstance(member.type.value_type, UnboundedWString)]@
246256
@(get_rs_name(member.name)): msg.@(get_rs_name(member.name))
247257
.into_iter()
248-
@[ if isinstance(member.type.value_type, UnboundedString) or isinstance(member.type.value_type, UnboundedWString)]@
249258
.map(|elem| elem.to_string())
259+
.collect(),
250260
@[ elif isinstance(member.type.value_type, NamedType) or isinstance(member.type.value_type, NamespacedType)]@
261+
@(get_rs_name(member.name)): msg.@(get_rs_name(member.name))
262+
.into_iter()
251263
.map(@(get_rs_type(member.type.value_type))::from_rmw_message)
252-
@[ end if]@
253264
.collect(),
265+
@[ else]@
266+
@(get_rs_name(member.name)): msg.@(get_rs_name(member.name))
267+
.into_iter()
268+
.collect(),
269+
@[ end if]@
254270
@#
255271
@#
256272
@# == UnboundedString + UnboundedWString ==

0 commit comments

Comments
 (0)