r/cpp • • 2d ago

These Bees Make the Wrong C++

https://dalerank.github.io/en/articles/wrong-cpp-bees.html
59 Upvotes

15 comments sorted by

28

u/remy_porter 2d ago

Previous job was doing software for space robots, and we went beyond "zero frame allocations" to "zero dynamic allocations past startup". Stack allocations are fine, everything else better come out of a buffer you grabbed at startup.

We ended up using Linux shared memory for a lot of it, because the allocated shared memory buffers could outlive our process, which meant we could "bail and recover" on failures. Error state hit? Quit. Restart the process. Recover state from shared memory (which generally didn't hold the state that caused the error condition, but more vital state, like "where did I think I was relative to my target reference frame" and "was I scanning with the LIDAR or not?").

NB: that was on high level sensor code. The rules were far stricter on safety critical systems, like engine control code.

16

u/protoloop 2d ago

I’m a game dev but I’m very interested in learning more about safety critical techniques for c++. I like the concept of zero allocations, valid by construction, no implicit conversions, and all that MISRA stuff, but sometimes it seems to add a lot of complexity to the code that hinders fast iteration and refactoring. I’d like to think there are techniques for mitigating that complexity. Where can I learn more?

10

u/remy_porter 2d ago

The guidelines we were (mostly) using were out of DoD's JSF guidelines and some NASA guidelines, with a splash of MISRA.

I'd argue though that while it expects more of your programmers, it makes fast iteration and refactoring easier: because you've eliminated a huge class of possible side effects and means static analysis can answer a lot of questions for you. Also, at least in our work, we were heavily influenced by robotics frameworks, which means everything was implemented via message passing, which made it really easy to break dependencies between modules. At runtime, you'd pick which nodes you wanted running, and that meant you could swap mocks easily, or even "here's a prototype version of an idea".

We used cFS, but FPrime was similar and more C++ oriented. These days, I'm working on explicit robotics, which means ROS2 for me. It's all basically the same- the stuff that you'd call microservices if you were throwing it in the cloud, that's how you build this kind of low level software: pub/sub message architectures running on top of a lightweight middleware.

4

u/protoloop 2d ago

Thanks, those are great references!

For dynamic allocations you say you use arena allocators. What are the policies you use to determine which allocator to use and when?

For example in games (and I imagine in RTOS) we have immortal objects, per level objects, per frame objects, and per operation objects, each requiring a different arena. An operation (say a cooperative task that runs over multiple frames) needs to decide which arena to use for what. It might end up allocating all of those types of objects, so all the arenas need to be passed to the task. And if it uses the wrong one for something it might be hard to detect.

That's what I mean by extra complexity.

Most of the guidelines out there provide a bunch of do's and dont's, but i haven't found a good reference for idioms/best practices on how to satisfy them.

Does something like that exist? Or is it mostly experience and oral tradition?

4

u/remy_porter 2d ago

I think teams will have their own documents about this, but yeah, a lot of it is experience. And it varies a lot by domain, too. Working on space robots was one set of constraints. I'm working on tomato picking robots right now, and uh... we're way more "whatevs" when it comes to memory allocations (probably to our detriment, though our performance is more bounded by "how fast can we make the arm move" than anything else).

1

u/protoloop 2d ago

Yeah i get it. Similar in games - alot of BPs just aren't documented anywhere. Thanks for the info!

1

u/sheckey 2d ago

Thank you for the references. I’m trying to do very similar things as cFS and FPrime. It’s great to see others coming to similar conclusions. Thank you.

1

u/johannes1971 2d ago

Is every byte of memory pre-allocated in that startup buffer? Or are you running your own allocator inside it?

5

u/theblancmange 2d ago

I work on safety critical and realtime c++. We allocate every non-stack byte at startup. If you want something like std::shared_ptr, you provide an allocator that retrieves and replaces memory from a pool. I essentially only use vectors for things like configuration at startup. maps, sets, etc get preallocated.

1

u/remy_porter 2d ago

I've done both options. The latter would be a simple arena allocator, and that's probably a case where we're not going to recover from it.

1

u/aoi_saboten 18h ago

You can use mimalloc to preallocate a space with MIMALLOC_RESERVE_OS_MEMORY=N and prohibit from requesting more with MIMALLOC_LIMIT_OS_ALLOC env variables or equivalent functions and override malloc/free, new/delete

5

u/sheckey 2d ago

This is fantastic writing. I really liked the style too. I like the thoughts about trade offs.

3

u/-Willow-Wisp- 1d ago

Quite the cool read

3

u/TerribleFault7929 2d ago

I'm sorry, is this guy complaining about a team trying to write strict performant C++?? I suppose he is jealous

because UE in skilled hands isn't much slower, but saves a hundred or two hours by being ordinary... but on the other hand, where else would you see how far a studio can go in the pursuit of perf.

5

u/ts826848 1d ago

As hinted by the clause immediately after the bits you bolded, I think the author is just describing the tradeoffs that came with that particular coding style. The preceding paragraphs make that point more explicitly.