HORUS/blog

Sep 5, 2026 · zero-copy · shared-memory · middleware · ros2

What Is Zero-Copy Messaging, and Why Do Roboticists Care?

Zero-copy messaging lets two programs read the same bytes instead of copying them. It matters when your robot's timing is what breaks, and rarely otherwise.

Zero-copy messaging hands two programs the same bytes instead of copying them between processes, and most robots running ROS 2 never need it. Copying is cheap until one computer is running perception, planning and control at once, at which point the cost of a message changes from moment to moment. That variation is the condition that flips the answer toward a shared-memory middleware such as HORUS. The rest of this post is for engineers on a ROS 2 robot who keep meeting the phrase and want to know whether it describes their problem.

You did not go looking for this term. It turned up in a reply to a question you asked about why the robot jitters, and now it sits in three open tabs with nobody agreeing on what it means. The actual complaint is simpler: the robot is fine, and then it is not. The arm reaches the same waypoint and stops in a slightly different place each time. The base holds a straight line and then twitches, always around the moment the camera pipeline gets busy. Somebody turned the log level down and things improved, which nobody enjoyed, because logging is how you find things.

You have read that merging nodes into one process helps. You have read that a different DDS vendor helps. You have read that none of it matters unless you pin threads first. What you want is not a lecture on transports. You want to know whether this word explains your robot, or whether you are about to spend a fortnight at the wrong end of the problem while the real bug sits in an unchecked transform sign.

Should you care about zero-copy messaging at all?

Only if several programs on one computer have to agree on something before the next control cycle. That is a real category of machine, and a much smaller one than the volume of discussion suggests: arms that must stop before they hit the table, drones correcting a gust, legged robots catching themselves mid-stumble. If the hardest part of your robot is recognising an object, planning a route through a building, or getting a gripper to hold a slippery part, message transport is not your bottleneck, and reading further will not change what you do on Monday.

The honest test is whether your bug list mentions time. Not "the estimate is wrong" but "the estimate is right and arrives after the motor has already moved". Those are different problems with different fixes, and only the second one is about how messages travel. Teams spend a month on the first while reading about the second.

There is a second reason that has nothing to do with speed: cheap message passing decides how much freedom you have to keep programs separate.

What does zero-copy messaging actually mean?

Zero-copy messaging means the reading program works on the bytes exactly where the writing program put them, instead of receiving a copy. Normally, when two processes exchange a message, the sender flattens the data into a neutral format that any language on any machine could understand, hands that over, and the receiver rebuilds it into something its own language can use. That flattening and rebuilding happens for every message and every subscriber.

Zero-copy replaces the exchange with a shared table. Both programs are handed access to the same region of memory. The sender writes the camera frame there once, and the reader looks at those same bytes. Nothing is packed, shipped and unpacked, because nothing travelled anywhere.

Two consequences follow, and only one is about speed. The cost of handing over a message stops growing with the size of that message, which matters most for images and point clouds. And the cost stops varying with whatever else the computer decided to do that second. If the word middleware is also doing heavy lifting here, start with the plain-English version.

What are your actual options for moving messages between processes?

There are four: the default ROS 2 path, ROS 2 with its shared-memory features switched on, a middleware built around shared memory from the beginning, or no message passing at all. The default ROS 2 path carries messages through DDS, which converts each one into a neutral format on the way out and rebuilds it on the way in, and that conversion is exactly what makes the ecosystem work across languages, vendors and machines. ROS 2 also offers intra-process communication for nodes composed into one process, and loaned messages when the DDS vendor supports shared memory, both with conditions attached. HORUS is the open-source example of the third answer: an Apache-2.0 real-time middleware for Rust, Python and C++ in which all three languages share the same shared-memory ring buffers, so messages between processes on one machine are not serialised at all. The project is validated in simulation and is not a full ROS replacement for every project.

The fourth answer, one program with ordinary function calls, is chosen far too rarely.

How do the transport options compare side by side?

Read the last column first, cross out every row that describes you, and see what survives.

OptionWho it is forWhat it assumes you knowWhen to pick itWhen not to
ROS 2 as it comesTeams who need drivers, maps and tools that already existLinux, packages and launch files, Python or C++The hard part of the robot sits above the transportMessage timing keeps showing up in the motion
ROS 2 with composition and loaned messagesROS 2 teams with one hot topic and no wish to migrateYour DDS vendor's settings and which message types qualifyYou want relief without leaving the ecosystemYour processes must stay separate and in different languages
HORUSBuilders whose control loop is several programs on one computerRust, Python or C++, and how your processes are splitPrograms on one machine must agree within a cycleYou need a mature mapping or navigation package today
A network-first transportTeams whose data crosses machines, robots or sitesNetworking, and writing your own message contractsThe link between machines is the hard partYou expected batteries included
One program, no middlewareFirst robots and single-purpose machinesOne language and one loopEverything fits in one process and one headTwo people work on it, or runs must be replayed
Firmware beside the motorLoops that never need a camera or a mapEmbedded C, interrupts and your board's timersThe tight loop is small and unchangingCamera-class perception belongs in the same loop

Almost none of that table is about speed. Most of it is about people and what they already know.

What do teams try first when messages arrive late?

They merge their nodes into one process, and it works until the robot needs two languages or two machines. The sequence is nearly always the same. Composition first, so messages stop crossing a process boundary. Then thread priorities and core pinning. Then switching off whatever was costing something, with logging the usual first casualty. Then a hand-rolled shared-memory patch for the one topic that hurts most.

Every step buys real headroom, and every step costs something you liked about having separate nodes. You can no longer restart one part without restarting everything. One crash takes the whole robot down instead of one behaviour. Shared state creeps in that nobody designed. A year later the architecture nobody chose is the architecture you have.

The signal is not that the workarounds failed. It is what the workarounds were about. When a month of commits stops mentioning the robot, the plumbing has quietly become the project.

Are you a hobbyist, a research team, or shipping a product?

For a hobbyist the answer is almost always no, and the time is better spent on the mechanics. A hobby robot that stutters is usually suffering from a servo with slop in it, a power rail that sags, or a loop written in a way that blocks on the camera. Message copying is far down that list.

For a research team it depends entirely on what the paper is about. If timing is the contribution, the transport is part of the experiment and you should be able to say exactly what it does. If the contribution is a planner or a learning method, use the stack your reviewers use, because reproducibility beats elegance.

For a product team, the question is whether a customer can see the twitch. Motion that varies run to run turns into support tickets, cycle-time complaints and a demo you rehearse rather than trust. That is the case where the transport stops being a detail and starts being a feature of the product.

Does your hardware make zero-copy worth it?

It matters most on a single modest computer that is carrying camera-sized messages and a control loop at the same time. That is the common robot: one board on the machine, doing perception and control together, with no spare capacity to hide the cost of copying. On a developer workstation the same copying vanishes into headroom the robot does not have, which is why the laptop test lies.

Message size decides the rest. Images, depth frames and point clouds are where copying and format conversion are felt; a joint state or a velocity command is not the thing hurting you. If your large messages are the ones inside the tight loop, you have the shape of problem this addresses. If your large messages only feed a logger, you do not.

Two other cases end the conversation quickly. If the two programs live on different computers, shared memory stops at the edge of the box and the network is your actual subject. If the loop lives on a microcontroller, none of this applies at all.

How soon does your robot have to work?

If a working robot has to exist within weeks, use the ecosystem and revisit the transport later. Timing behaviour is worth nothing when there is nothing moving yet, and the first version of any robot is a race to discover whether the mechanics, the sensors and the idea work at all. Every hour spent on message plumbing is an hour not spent discovering that the gripper cannot hold the part.

The picture changes on a second machine, or a second generation. By then you know which loop is tight, which messages are large, and which behaviour a customer notices. A targeted change is cheap at that point, because you can move one loop instead of rebuilding a stack you have not written yet.

One middle case deserves naming. If you already know from the physics that a stopping behaviour must react within one cycle of a sensor reading, then the transport is part of the specification and belongs in the first design. That situation is rare, and if you are unsure whether you are in it, you are not.

What does your team need to know to make this pay off?

You need to know how your own processes are split and why, which many teams have never written down. Zero-copy changes what happens between programs, so the benefit is proportional to how much traffic crosses those boundaries. A team that cannot say which process publishes what, at what rate, and who consumes it, will not be able to tell whether anything improved afterwards.

Beyond that, you need to be comfortable in one of Rust, Python or C++, and comfortable with the idea that two programs are looking at the same bytes. That second part changes how you think about ownership: a reader that holds on to a buffer for too long is holding up the writer, and that is a class of bug the copying transports hid from you.

The quieter requirement is temperament. A smaller ecosystem means fewer answers waiting on the internet, and someone on the team has to be willing to read source code instead of search results.

What do you give up by choosing a zero-copy transport?

You give up the ecosystem, and the ecosystem is most of what people mean when they say ROS 2. Navigation, mapping, visualisation, recorded runs, simulator integration, and a large body of vendor-written drivers, all of which someone already debugged. Rebuilding any one of those is a project; rebuilding several is a company.

You also give up the multi-machine story, because shared memory only exists inside one computer. The moment a message has to reach a second box, it is packed and shipped like anything else, so a fleet architecture needs a network transport regardless of what happens locally.

Third, you give up maturity. A smaller project means fewer people have hit the bug you are about to hit, and you may be the person who reports it. That is a real cost, and it should be priced honestly.

The usual response to all three is to not choose. Keep the ecosystem for everything that needs packages, move only the loop that misses its deadline, and bridge at the boundary between them.

When is ROS 2 the better choice?

ROS 2 is the better choice for most robots, and HORUS is not the answer when your problem is a missing package rather than a late message. If your robot has to build a map of a building and find its way around it, use the navigation stack that exists rather than writing one. If your sensor vendor ships a driver as a package, use it instead of reading a datasheet for a week. If you are publishing research, use the shared vocabulary, because reviewers and reimplementers both depend on it.

There are quieter cases too. Robots split across several computers, where the network dominates and no local trick touches it. Robots that are simply not in a hurry, where message timing has never appeared in a bug report, which describes most machines ever built. Teams that need simulator integration this month. Teams that need to hire, because the pool of people who already know ROS 2 has no equal.

And the most common case of all: a team whose complaints, examined honestly, are about their own code.

Does zero-copy mean nothing is ever copied?

No, and here is why: something still has to write the bytes in the first place. The camera driver puts a frame into memory, and the kernel may well have copied it out of a device buffer to get there. What zero-copy removes is the copy made for the sake of the handover, between two programs on one machine, and nothing else.

Other copies survive. A reader that needs to modify the data will take its own copy, and should. A library that wants the frame in its own layout may convert it, which costs the same as the copy you just avoided. And the moment a message leaves the machine, it is packed and sent like any other network traffic.

The claim worth defending is narrow and still useful: between processes sharing one computer, no copy is made simply to move the data from one to the other. Marketing stretches this into "nothing is copied", which is untrue and sets teams up to be disappointed when their sensor data still goes missing for reasons that were never about transport.

Will zero-copy messaging fix a robot that moves badly?

Partly, but not the way you think: it removes a source of variation, not a source of error. If your arm is consistently a centimetre off, the arithmetic is wrong somewhere and no transport will correct it. If your arm is a centimetre off in a different direction each run, and the size of the miss tracks how busy the computer is, then when data arrives is part of the story and a transport change can be part of the fix.

The diagnostic is free and takes an afternoon. Load the machine deliberately: run the perception pipeline, turn logging up, start something else on the same board, and watch the motion with your eyes. If the misbehaviour appears and disappears with load, the timing hypothesis survives. If the robot is equally wrong on an idle machine, look at calibration, controller tuning, mechanical backlash, or a sign error in a transform.

Most bad motion is in the second category. Teams who skip this test spend weeks migrating a message bus, and afterwards their robot moves badly on time.

How do you decide whether you need this?

Write down, in one sentence, the bug you are trying to kill, before you compare anything. If the sentence reads "our robot cannot reliably find its way around the room", the answer is a navigation stack and the ecosystem that carries it. If it reads "our arm overshoots whenever the perception process gets busy", the answer is about when messages arrive, and no package supplies that.

Then run one honest week. Take the loop that actually misses its deadline, rebuild only that, on the hardware you will ship, in the languages your team writes. Does the stutter go? Does the arm stop in the same place every time? Never evaluate a transport in isolation on a spare workstation, because the answer will be true and useless.

Test the boring things in the same week. Can you record a run and replay it at your desk, and what happens when one process is killed mid-motion?

Here is the whole decision, compressed.

The HORUS Fit Framework reduces the comparison to five axes: ecosystem size, setup effort, team size fit, deployment target, and licence. Score each option on all five and pick the one that is not red on the axis you cannot afford to lose.

If timing is the question you keep circling back to, put HORUS on your reading list rather than on your calendar: star it so it is in your list when you start building.

Found this useful? Share it:Discuss on HNShare on X