“The Lost Art of C Structure Packing” now covers Go and Rust

I have issued a new version, 1.19, of The Lost Art of C Structure Packing.

The document now covers Go and Rust as well as C, reflecting their increasing prominence as systems-programming languages competing with C and being deployed in contexts where structure-size optimizations can be of some importance.

TL;DR: C alignment and packing rules map over to Go in the most obvious way except for one quirk near zero-length structure members. Rust can be directed to act in a C-like way but by default all bets are off.

25 comments

  1. FWIW you have the wrong year for this update in the version history at the end of the document.

    1. >C combines the power of C with the flexibility of C.

      I’m moving in a Go-ward direction, though. Power of C, flexibility of C, and garbage collection. That’s one reason the document now covers Go.

  2. I find it interesting, but not the tiniest bit surprising, that Rust makes you go out of your way to lay out a structure in the way you specify instead of letting it rearrange things willy-nilly. And, I suspect, it complains about it the entire time and probably turns off some optimizations.

    Tell me again why Rust is a good idea for embedded systems? An embedded application almost always needs to have structures for communicating with the hardware laid out precisely, down to the bit.

    1. >Tell me again why Rust is a good idea for embedded systems?

      I think you’re being a little unfair here. Rust does let you select C layout rules and suppress field reordering, after all. Having the default go the other way is reasonable; most structures don’y need reordering suppressed.

      1. “Rust does let you select C layout rules and suppress field reordering, after all.”

        At what cost?

        1. >At what cost?

          Um, based on my understanding of how compilers work, all you’d give up unless the compiler author(s) did something profoundly stupid would be the small space optimization the compiler could otherwise collect by reordering.

            1. >Well, there’s cache locality, which can toast performance…

              Duh. If you care, you rearrange the structure elements by hand. Document cited in OP demonstrates this is neither rocket surgery nor brain science.

            2. Memory bandwidth, too. The amount of main memory that’s practically accessible _per core_ in any given amount of CPU cycles is dropping real fast in modern systems. I see this as the most important reason for caring about structure packing these days. The few insns you’d spend unpacking your data on access (e.g. by placing boolean or subword-sized data in a bitfield) are immaterial compared to the gain of slashing your memory bandwidth requirements. (And this is just as much true for power requirements as for raw speed. Data movement, not calculation per se, is by far the biggest power hog in modern hardware!)

              1. >The amount of main memory that’s practically accessible _per core_ in any given amount of CPU cycles is dropping real fast in modern systems.

                That is an interesting claim which, if true, is worth a mention in the document. What’s your evidence?

                1. Well, simple calculation does seem to bear this out. While admittedly I just knew of this as “folklore”, here is a reference I just found which has a rather, um, “unforgiving” comparison between the MOS 6502 and a modern Intel multicore chip! (And intuitively, the more cores you add, the more contention you’ll get for a relatively-fixed amount of total memory bandwidth.)

        2. Since the Rust backend is LLVM: presumably the same cost that C imposes for using the C layout rules…?

    2. I find it interesting, but not surprising, that C makes you go out of your way (via the volatile keyword) to make it access memory in the way you specify instead of rearranging and optimizing accesses willy-nilly. Tell me again why C is a good idea for embedded systems? An embedded application almost always needs to have memory reads and writes timed precisely, down to the microsecond.

      1. Nice try. The embedded systems I work on have no such exact timing constraints. They do, however, have many detailed layouts for structures in memory corresponding to CPU and peripheral registers.

      2. volatile makes C access memory in crazy ways that only the deranged imagination of a hyperoptimized code generator could possibly expect.

        C has barrier operations now. When it really counts, only a bunch of machine-specific functions for talking to registers will do.

  3. I see that you also added a mention of static_assert.

    Since it can be quite useful indeed, it might be worth noting that it is not necessarily limited to C11. Several variants for C89 can be found on the web — some based on division-by-zero at enum initialization, others on negative array length etc.

  4. C++ since 11 has an ‘alignas’ keyword, which makes standard what used to require extensions like #pragma pack on MSVC, something similar in GCC, C itself has a standardised equivalent. It might be worth showing a portable binary protocol type structure definition (for example) in each of the languages that support such explicit definitions.

    This being, IME, the most likely application area where you absolutely need to worry about structure layout.

  5. @esr: From what I understand of Rust, structure packing is a detail the compiler handles for you so you don’t have to be concerned about it.

    Rust does let you select C layout rules and suppress field reordering, after all.

    And the question is when you might want to. Offhand, I suspect you won’t. The things that might make Rust a poor choice for the language to use won’t be in areas like that.

    >Dennis

    1. >Off-topic, but does anyone know what’s going on over at FreeBSD?

      No politics in this thread. If it happens despite my injunction I will delete messages.

Leave a comment

Your email address will not be published. Required fields are marked *