TL; DR I am of the opinion that some of the performance bottlenecks we still see in ROS 2 can be traced back to rosidl design. There are benefits to language-specific runtime representations and vendor-specific serialization formats, but efficiency is not one of them. Other designs may be better suited to the kind of data streams that are common in robotics. In that sense, GitHub - Ekumen-OS/flatros2 may be an interesting conversation starter.
Howdy! I don’t post often on ROS Discourse but I thought this may be worthwhile. The Physical AI rebranding of robotics is drawing attention and resources, and in that whirlwind I keep seeing new libraries and frameworks showcasing performance figures that seemingly obliterate those
of ROS 2 (like dora-rs/dora-benchmark, but there are others). Why? The total amount of engineering resources invested by this community in ROS 2 far exceeds that of any other new project and yet I still find myself second guessing ros2 topic hz output. Well, I’ve been around and about for a good while now, and I have a hypothesis.
rosidl is one the oldest corners of ROS 2. C and C++ message generators were first released with Alpha 1, each with their own runtime representations: simple enough, language-specific. The first few DDS based middlewares (like opensplice) had their own vendor-specific IDL to comply with, for interoperability and full feature support, and so type support code and internal runtime representations had to be generated. A decade later ROS 2 is still bound by this design.
Zero-copy transports cannot cross the language boundary because there’s no common runtime representation, and because in-memory layouts are nonlocal, even for the same language their scope of application is extremely narrow (so narrow not even standard messages qualify). Middlewares (de)serialize messages to vendor-specific formats that keep them traceable on their domain and afford us features like keyed topics, whether that makes sense for a given form of data or not. Repeated (de)serialization of images and pointclouds (and other forms of multi-dimensional data) certainly does not help speed.
I honestly didn’t know if there was a way out of this. Some of these shortcomings cannot be fixed out of tree. So I started Ekumen-OS/flatros2 with some Ekumen colleagues as an experiment. It turns out there are lots of pitfalls and limitations but there is a way. Ekumen-OS/flatros2 is NOT it, however. A true solution (I believe) must be a core solution, and Ekumen-OS/flatros2 is just an exercise on how that future solution may look like i.e. messages as language-specific views to contiguous memory layouts, bounded on write, unbounded on read. The choice of flatbuffers and iceoryx2 was informed but arbitrary. Both have interesting properties nonetheless.
Hope this helps kickstart a discussion. There’s no fundamental reason why ROS 2 cannot perform near I/O limits. And who knows, maybe there’s enough momentum to sort out message compabitility too while we are at it (and I’d very much appreciate backwards and forward compatible bags).
Glad to see this work out in the open, what good timing !
From our perspective flatbuffers is also very compelling on the RTOS side (using with zenoh pico) . I personally have already played with a “hacky” rosidl_adapter to limit the buffer size of a string for embedded as a [ubyte:64] struct (for static allocation of a given FBS with flatcc). I’m excited to see where this goes, I do think there is a lot of potential performance gains being left on the table when comparing where we are currently at with other less large/newer projects.
I’m also quite curious about this. However, it would have to be proven that the serdes savings are not bought by runtime inefficiecies (i.e. losing vectorization when not using language/library-native memory layout).
So, what’s the proposal? If I understand you correctly, your theory is that rosidl is responsible for a significant part of the gap between ROS 2 and other frameworks, which seems plausible to me, but what part should we change?
Of course these things are related, but I’m trying to understand what we could concretely do to improve this. I think zero-copy between processes using the same language is possible (https://docs.ros.org/en/humble/How-To-Guides/Configure-ZeroCopy-loaned-messages.html) using loaned messages and even with the standard rosidl c++ structs if you’re using plain old data (no strings or sequences). To do this between different languages you would need something like flatbuffers. And if you want to use something like flatbuffers or capnproto or the like, then I think you could do that by adding additional rosidl generators. We have an “official” one for python that provides simple Python objects similar to the ones in ROS 1 based on the message definition, you could have additional ones for C++ and Python that present different data structures (think #include “sensor_msgs/msg_flatbuffer/image.hpp rather than #include “sensor_msgs/msg/image.hpp. That would allow users to use these other data structures for any user defined message type, but if you want it to be very efficient then the middleware needs to understand these new types, otherwise you’re limited to copying from your preferred user facing type to the type the middleware understands or serializing it to the wire format that the middleware understands, neither of which are particularly efficient nor do they lend themselves to zero-copy.
But even in those cases you’d still have the rosidl pipeline (type definitions → machine readable type definition → language or serialization library specific code).
It looks like you’ve avoided the need for a new rosidl_generator_flatbuffer-like packages in your flatros2 PoC by using some reflection (a la flatros2/flatros2/include/flatros2/message.hpp at 8a8ad51ffbe363c8e4d8909b548de075d4b26ceb · Ekumen-OS/flatros2 · GitHub) and you’re using rosidl_typesupport_introspection_cpp to handle support in the middleware, which is nice because it’s above the rosidl/rmw level mostly. However, to gain more optimizations, or for a marshaling library like protobuf or arrow (or even to use flatbuffer better), you’d probably want some build-time step, which is where a rosidl_generator_cpp_X/rosidl_runtime_cpp_X/rosidl_typesupport_cpp_X like set of packages would come in. With that in mind, I guess I don’t know which parts of rosidl need to change, because it seems like, at least in theory, it should be possible to solve these performance problems.
So is the proposal just to build some of the packages I described above, or is it to change the “rosidl pipeline” somehow? Or is the conversation more about changing the defaults in some of these cases, in addition to building the alternatives in the first place?
Maybe the answer is just making what we have better? For example, I believe (someone correct me if I’m wrong) the dora-rs benchmarks are comparing against rclpy? If that’s the case then we could possibly make rclpy’s story better by having a rosidl_typesupport_XYZ_py set of packages, so the middlewares could handle PyObject * directly from our user’s Python code. Right now we have to convert from the PyObject * to our C-style struct for the message before handing it to the middleware via rcl_publish()/rcl_take*(), which is very inefficient, especially for large data structures like images and pointclouds. Even though we’ve tried to improve the performance there using optimizations and things like numpy.
I hope so too, and there’s a REP for how to do this, it just needs resources. And I personally believe the strategy in what was proposed as REP-2011 is a “yes, and” for the idea of alternative serialization libraries, as I believe it complements features that people already use to evolve types (like optional fields), in most libraries I’ve studied at least.
Off the top of my head (and I may be forgetting about a few things):
C and C++ runtime representations map message members straight to struct members. That makes it hard to evolve them. In particular, it is really hard to decouple storage from interface in a backwards compatible way. The flatros2 experiment approximates the C++ runtime representation using lvalue reference members and operator overloads. C is too far gone. Python is a lot more forgiving, with data descriptors and all.
Loaning APIs all the way down to the rmw layer seem to have been designed for POD messages. They assume (and require) message types fully define in-memory layout and there’s little margin to defer that to runtime – largely because there’s also the assumption that message type support information is invariant. flatros2 solves this with dynamic typesupport and pre-serialized message prototypes, but those have a memory footprint that may be undesirable.
Also, I have yet to find a zero-copy transport with support for inter-process pipelines, though it appears to be in the roadmap for iceoryx2. Think process A allocates shared resource M and passes it to process B, process B modifies resource M and passes it to process C, and so on. It’s a common pattern that right now cannot escape (de)allocations and copies.
That needs attention, yes. I’d think that as long as memory layout and access patterns are appropriate for modern processor architectures we need not sacrifice performance. The flatros2 experiment already shows how one could go about numeric array views, using numpy.frombuffer (here) and std::span<T, N> (here). Numpy can already accommodate multi-dimensional data. For C++, there’s std::mdspan (or kokkos::mdspan if the jump to C++23 turns out to be a bit much).
Are the C/C++ data structures generated from the type definitions the problem that needs to be addressed?
In part. Getter / setter APIs would have been easier to evolve.
Or is it the need to do zero-copy?
There’s some of this too. Many robotics applications and stacks are confined to a single host. Zero-copy transport can take you a long way in that case, and we know that, intra-process communication in rclcpp is today’s usual answer to performance bottlenecks.
I think zero-copy between processes using the same language is possible using loaned messages and even with the standard rosidl c++ structs if you’re using plain old data.
And if you want to use something like flatbuffers or capnproto or the like, then I think you could do that by adding additional rosidl generators.
That’s all true, but the point I’m trying to make is that those are not really viable. IMHO ROS 2 biggest advantage is its ecosystem and community. If I switch to a middleware that puts heavy restrictions on the messages I can use (your message has a header with a string frame id? no zero copy for you), or I simply change the messaging format, I lose that advantage.
It looks like you’ve avoided the need for a new rosidl_generator_flatbuffer-like packages in your flatros2 PoC by using some reflection
That’s right. flatros2 actually uses flexbuffers, but this was just a shortcut to keep things “simple”.
you’d probably want some build-time step
Absolutely. For proper integration I’d use true flatbuffers and compile .fbs schemas adapted from rosidl interface files.
Maybe the answer is just making what we have better?
I think there’s a separate discussion to be had about rclpy.
So, what’s the proposal?
That’s a good question and the toughest one to answer. I’ll preface with some of the premises I’m working with:
Messaging improvements are rosidl improvements. There’s just too much code out there that depends on rosidl generated code to move away from it, and because of it, an out of tree alternative will either get marginal attention and go to waste or show promise and fragment the community.
rosidl changes must be backwards compatible (or at least approximately so), for the same reasons laid out above.
So with that in mind, there’s two parts to this: (a) the (re)design of the messaging system, and (b) the backwards compatible implementation and rollout.
For (a), flatros2 hints at onepossible design: messages encode the structure of the data but it’s not until instantiation (or reception) that data materializes. A camera driver doesn’t publish some image, it publishes a QVGA image with BGR8 encoding, so it has all the information it needs to bound what’s unbounded in sensor_msgs/msg/Image. An image viewer doesn’t need to know that, it only needs to know that it is subscribing a topic with sensor_msgs/msg/Image structure. This notion helps separate message interface (view) from data (buffer), which can be managed by the middleware if enough information makes its way down there.
For (b) I only have some of the pieces of the puzzle, so bear with me:
Build next-gen rosidl type support for middlewares
Transport buffer as blob, optionally duplicate specifics (e.g. key fields)
Should there be a standard buffer format for middlewares to rely on?
Built and installed for next-gen generators only
Extend rmw/rcl/rcl* APIs to take additional input for loaning
And then a long tail of deprecation cycles for future to be current.
FWIW I’m not going to solve this on a Discourse post, even if I tried. There are multiple REPs in that last bullet list alone. This is only feasible as a community effort.
That’s fair. I’m going to reply to several things, just to add to the memoranda here, but I agree this will need a champion and REPs (or their equivalent).
Hmm, perhaps, but I’m also not totally convinced. For instance, one of the main issues is with how strings and sequences are presented, and if you went with std::string and std::vector (as we did, and keep in mind that things like string_view and spans didn’t exist at the time), even method based access wouldn’t have made changing the underlying storage or ownership easier. At the time we thought it made more sense to use the existing containers in the STL, rather than rolling our own, and I think that was probably the right call at the time. Even now, I would try to fit what we want into one of the standard approaches unless that was absolutely impossible.
I don’t disagree, but zero-copy brings with it several constraints/issues that may not be ideal for every situation (maybe even most situations), e.g. dealing with exhausted shared resources (in slow-consumer, fast-producer situations). Also, it’s just my hunch (someone may convince me otherwise), but I do not believe that most robotics frameworks actually use zero-copy, maybe except for real-time control related spaces, and that’s actually fine for most situations. In my opinion, unless you need zero-copy for extreme performance or real-time related issues, making a copy (or serializing and deserializing) is a good solution much of the time.
I can see why you think the current options are not really viable, since they have a lot of restrictions, but I don’t think those restrictions are impossible to overcome in the current system. For example, if we had a new C++ rosidl generator that used std::string_view and/or std::pmr::string then we could probably allocate space for the strings and sequences from a shared memory segment using a custom allocator. The flexbuffer you’re using is kind of similar. Then if we added a new typesupport package that combined the new C++ generator with each middleware (cyclone/fastdds/zenoh), we could probably support zero-copy with variable length strings and sequences, which would then allow the standard messages to work. Now, you’d still have to set upper bounds on the available shared resources.
Also, related to that, we often talked about a way to set artificial limits on specific unbounded message fields on a per application basis, e.g. for your real-time controller you could say that the transforms.header.frame_id field from the tf2_msgs/msg/TFMessage message is limited to a string of 1024 characters, such that if some other ROS node sent a longer value for that field, then the message would discarded by your controller node. I don’t know if it properly got described, but I remember we discussed it while working on Zero Copy via Loaned Messages (I think @mjcarroll worked on that one too). With something like that you could potentially support zero-copy as well, since you could at least predict the maximum size of the data structures. But that would still require a new rosidl generator and require both sides to use that data structure, loaned messages, and perhaps other restrictions. But I don’t see us getting to a place where zero-copy is always on for all (or even most) situations between processes on the same host.
In any case, most of what I’ve described above requires a new rosidl generator, which I believe you think (correct me if I’m wrong) means fragmentation in the community, but I don’t think that’s necessarily the case. Now, I can see how this could lead to fragmentation due to some nodes using the features and some not using them (e.g. I want to use a laser scanner driver, but it uses normal messages and so can’t do zero-copy, so I fork it and change it to use the method-based messages and loaned messages). But from a different perspective, we can introduce new rosidl generators, folks can use them to get access to different features and/or different serialization libraries and still not cause fragmentation in the sense that existing tools and nodes will work with them, just maybe not as efficiently as could be possible if you were to rewrite the tools/nodes to use the new thing. Meanwhile, pr’s to update the tools/nodes to use the new message structs would be lower risk because they would still work with non-updated nodes (e.g. if you were to update rviz to use “future” message structures, nodes that publish the old style will still work with rviz after the change). So, I’m just not convinced that it will cause fragmentation in the ecosystem, but I could be wrong.
This is actually very similar to a related feature which folks at nvidia (@HemalShahNV) have been talking about which is to have a “rcl tensor” which in part boils down to a version of standard messages that pass around handles to data in the GPU rather than the data itself, and ideally doing this in a way that doesn’t fragment the ecosystem or eliminate the benefits of the approach.
And I think what I’ve been talking about here (add new rosidl generator, demonstrate it’s value, maybe eventually make it the default via tick-tock) is in part what you’re talking about for “part (b)” as well. The only thing I’m not sure about is why this new data structure API must be a view onto a contiguous buffer. And if we decide that necessary for some reason, then maybe using an existing approach to that, like flatbuffers, might make more sense than defining our own API. There is value in having a ROS specific API for interacting with data, to protect ourselves from vendor lock-in, but there’s always a cost associate with that abstraction. For our current member-based structures that means copies or (de)serialization directly to our structure, and for a method based API it might require the same under the hood, or perhaps storing some of the fields twice (once in the original form or serialization buffer and a second time as the requested format in the getter) and keeping them in sync, etc. There’s something really nice about the simplicity of the member-based structure we have now, in that once you have a copy of it, there’s very little magic behind it, making it easier to understand and predict.
This isn’t something we necessarily have control over in order to use DDS most efficiently. For other middlewares like zenoh this might make sense, but essentially this is what the DDS middlewares are doing themselves anyway (transport as binary data and send some data in duplicate or hashed).
Do you mean in-memory layout of the data structures or the layout of serialized data? For the latter, we have that and it’s CDR right now. But that could be changed, because we’ve tried to be careful not to assume CDR anywhere (e.g. we store this information in rosbags).
That’s true for STL types in C++, yes. For POD types and C, some accessor pattern would have helped change without affecting downstream users. It’s interesting to see the path that other data interchange formats like Protobuf took on this regard. It may still be relevant to any future change.
I agree with that, and standards have evolved to the point we would need fewer ad-hoc abstractions, if any.
I agree. FWIW I don’t think ROS 2 should transition to zero-copy everywhere. I do think it should be simpler to use it efficiently when necessary.
Well, I’ve found myself resorting to rclcpp and intra-process comms, sometimes forced to, whenever there was an image or a pointcloud at play and I needed steady throughput. I don’t discard that may have been a consequence of the specific rmw implementation I was using at the time, and that things may improve in the future even if rosidl were to stay the same. I do believe that faster and cheaper data transports would be beneficial for the sort of system architecture that ROS 2 proposes (as opposed to single process monoliths, for instance). Zero-copy transports are one option.
I agree, and the idea of loaning messages from the middleware puts the allocation responsibility close enough to where it can be leveraged for transport. The problem is that we communicate very little to the middleware, and so we either end up overspecifying the message type or adding catch-all upper bounds.
We are on the same page here. What I think would fragment the community would be changes to message runtime APIs (current, future, any really) that force rewrites. Most maintainers out there are volunteers.
I may have perhaps oversimplified things a bit (though I still think a simple binary blob would take us a long way). To get the most out of compute hardware, data layout has to match access and processing patterns. If I think of a generic modern CPU (SMP, L1-3 caches, SIMD ISA extensions), that data layout looks like a memory aligned linear data structure. If it’s fixed in size, it also simplifies allocation and queueing algorithms. It so happens that zero-copy transports I know of put similar requirements to work over shared memory. Views are just the language specific adapters that make it not a nightmare to work with that data, which won’t be just a POD struct in general (at least not until the user has provided enough application specific details to turn it into a POD struct, if it applies). Note that what makes sense for a CPU may not make sense for a GPU or some generic network model. That’s an interesting discussion to have.
The former, but it may be both if they are one and the same. There’s an argument to be made about serialization formats adapting hardware specifics. I wonder how relevant that is for a robot. I’m past 30 and I think I’ve never seen a big-endian machine.
the protoros2 follows what @wjwwood said the official way to support protobuf serdes(typesupport_protobuf, generator_protobuf, typeadapter_protobuf etc.)
Hey I might be well placed to revive this discussion,
I read that “cross language serde brings overhead and rosidl can be improved” (thread is very long I couldn’t follow everything).
I actually measured and improved this for Python. I developed cydr, a fast XCDR1 serde library for my pyzeros project. It is at least 3x faster than rclpy’s rosidl, and 21x faster on big messages.
As a Python dev, I created it specifically optimised for Python. I got faster by:
Minimizing python API calls from C (I use Cython). I spent so much time finding the fastest way to create each Python object from C
Using numpy arrays
Filling numpy array by copying memory
So yes, it is possible to have some major speedup by using a IDL made for a specific language. I also found zero-copy extremely hard to do in Python because the language always wants to create a new object. So I didn’t implement it, it is however easy with numpy but a big pain in the butt with everything else especially strings.
Strings actually illustrate the problem the best. There are 3 ways to get an array of strings, all because Python cannot do an array of null terminated strings:
List[bytes], the slowest with many objects creation and copies. And acess is also slow
numpy 2 bytes array, those are contiguous arrays of fixed length utf8 bytes. So one signle Python object creation, but still small copies in C. And it wastes a lot of memory because the array is fixed length but not the data. However access is fast.
C wrapper around the data. So it looks like a List, but behind the scene it’s a fixed memory region. Fastest object creation, but slowest to access and itterate over all the data.
Thanks for bumping this thread up, it’s been an interesting read
It’s true that ROS 2’s serialization overhead has been historically catastrophic to say the least, especially for python, though things are slowly finally turning around these days. With the asyncio node being only in Lyrical and later though, it’ll take a very long time for gradual adoption to fix something that should’ve been patched like 5 years ago.
At least from my perspective, all of the extra layers of abstraction in the message stack have all materialized so much extra overhead (and CPU usage increases that nullify an entire generation of hardware gains) for what now looks more like cargo culting around approaches that have shown dubious benefits in practice. All the touted DDS reliability is nowhere to be seen when each vendor treats ROS integration like an afterthought. There’s a mile wide split amongst a dozen options with all the dev effort spread out so thinly that barely anything gets tested properly, and everyone’s using some custom barely reproducible config. The roscore has reincarnated itself as roudi, zenohd, fastdds_discovery_server, proving yet again that a centralised way to route was always the most efficient and reliable way.
We know what people building production robots have switched to, it’s not zero copy, but no transport at all. 3D lidars and 4K cameras processed by single monolithic nodes with a dedicated direct pipeline with hardware accelerators anyway, and the end result is low bandwidth aggregated data that goes out. I’d like to see someone try iceoryx zero-copy to the GPU? To VIC and NVENC? Yeah right. So it’s not really useful conceptually. The rest of the topics are all small and high rate which is just about the worst case scenario for the current IPC.
Leaving aside the large number of companies using DDS in production effectively, despite its complexity, and the effectiveness of Zenoh as an alternative, despite the core concepts being the same, how is it DDS’s fault that vendors treat ROS integration like an afterthought, and how would anything else change that?
fastdds_discovery_server doesn’t do routing. It’s for providing a local proxy to remote discovery in order to speed up discovery. zenohd can do routing but doesn’t have to - and won’t unless you configure it to do so, which you should when you need it, such as for going through firewalls and across the Internet. roudi should be considered a different case from the other two, since it’s managing a shared memory store. roscore didn’t do routing, either, it was a phone book system. Centralised routing of the data is not the most efficient, but it is the easiest to build and set up.
This sounds anecdotal, at best.
Add rcl::buffer support to the IceOryx RMW and this will work.
Yeah that’s what I’m talking about. The reliable DDS setup is to use it natively, not through two abstracting layers on each side, to run it on ethernet, get a consultant to configure it specifically for the network layout at hand, maybe even with a static set of endpoints. The kind of setup that can work well I’m sure. Unfortunately that’s not really what robotics as a whole has really demanded from it. We’ve come in expecting that to work for fleets over wifi, and for all kinds of variable message sizes at stock configs. With intense fiddling one can get it workable, but lots of people seem to quit on the way. And even then you’re stuck with the overhead.
Zenoh on its own lives up to the hype. Zenoh with ROS? I can swap it for cyclone not really notice to be honest. In some cases I’ve even seen it perform worse.
Centralized transport sure isn’t, but why not routing? The STUN instead of TURN idea, sure you’re not gonna send all packets over the router, not even roscore does that, but knowing where to set up those P2P connections to with just one one lookup is going to be better with a centralized cache compared to a convoluted discovery process that brings down networks with multicasts on its best day. I still switch between ROS 1 and 2 on the daily and despair when all RMW options take up to a minute to figure out what nodes exist and set the system up, while the old approach is always instant and does not really struggle to deliver any messages promptly with minimal CPU load. Like what have we gained there exactly? Maybe I’m not using 500MB clouds on the daily but that’s hardly the average use case for anyone, but I can replay a bag at 20x realtime on ros_comm and it still works. That’s where I see the dubious benefits.
Well I’m just saying, it was great idea to leave comm maintenance to someone else who can them get all the blame for not doing something they didn’t really have a plan on doing. Then there’s two sides pointing fingers at each other saying the other one should do something about it.
Just like the usual “DDS is used in production” claims let’s be frank.
Sure Boeing mounts turbofans on its planes, they can fly much higher and faster than these Cessnas we’ve got, surely switching them to jet engines will improve them over the four cilinder prop. Also let’s add this 100kg strut to hold the new engine…
Will it? How? At the end of the day you still have to send the data over PCIe, and even shared memory architectures don’t really allow swapping pointers, so I don’t see how it would. Maybe you get one copy instead of two (ram->vram instead of ram->ram->vram), but that’s really the most you can do and it’s not the one that gives you the most overhead.
I was specifically referring to companies using it in production robots via ROS.
This, I don’t disagree with. Distributed systems is hard, no matter what technology you use. One of the primary drivers for doing the rmw_zenoh work is to simplify the majority case. If you’re building a production system, even if you use pure Zenoh, you’re going to have to do the work to understand your communications environment and communications needs, and set up your system to work with them.
Have you filed issues about this? Have you brought it to the PMC meeting for discussing potential improvements? Have you proposed potential improvements?
Have you also measured the drop in performance over pure Zenoh (or over “no transport at all”) for your application and confirmed that the drop is more than your application can withstand, and outweighs the functional benefits that are gained from using ROS?
To be clear with terminology, routing is the transport of data. Looking up where to send it is lookup, or discovery in DDS’s terminology. roscore provides a purely centralised lookup service. Plain DDS uses a purely distributed lookup method. DDS with the FastDDS discovery server uses a hybrid approach, as can Zenoh (although it can also do pure distributed lookup). In my opinion the hybrid approach is best (and Zenoh’s is particularly flexible) because it gives you the benefits of distributed discovery with a robust caching mechanism to prevent single-point-of-failure problems (which roscore’s approach has) and reduce discovery overhead.
It’s also worth noting that, if you really prefer the 100% centralised approach of roscore, even for remote connections, both Zenoh and DDS allow this in their design. You just need to implement it - and with Zenoh and FastDDS, at least, that’s just a configuration change. I don’t think the benefit would outweigh the drawbacks, though.
This is exactly how the FastDDS discovery server and Zenoh’s daemon work. They only need to use distributed discovery for anything that is on another computing node - and with Zenoh, at least, you can hard-configure the location of other daemons as well as well if you like. DDS’s purely distributed discovery bringing down networks with multicasts is, in my opinion, a solved problem, other than perhaps the default configuration not working that way (yet).
I think it’s far more sensible to leave communications protocols to the communications experts and have us over here in ROS land be merely users of them. Then we can focus on the bits we’re good at. The ROS 1 protocol has numerous well-known shortcomings that would have taken significantly more effort to fix.
We can argue about who’s stories are more anecdotal all day, but I’m happy knowing that I see robots every day in production use in the world working well with DDS or Zenoh, not “no transport at all”.
This is a slippery slope argument.
If you only need the data to be in VRAM and use it in multiple nodes (for example, the camera driver and a node that processes the images), rcl::buffer with IceOryx would not require the actual data to go via RAM, the camera driver could place it directly in VRAM and the other node(s) could use it there. If you need the data to be used by the CPU as well as the GPU, then zero copy is not possible at the hardware level.
While I appreciate that you have probably spent some time thinking about this, to me it seems irrelevant to the conversation that is currently going on?
Additionally, there are several “tells” here that this is fairly generative AI heavy text that you dumped as a non-sequitur of an actual human conversation.