C++ Weekly - Ep 410 - What Are Padding and Alignment? (And Why You Might Care)

Поділитися
Вставка
  • Опубліковано 23 гру 2024

КОМЕНТАРІ • 89

  • @kuhluhOG
    @kuhluhOG 11 місяців тому +54

    One tip when rearranging data members to avoid padding: Put your biggest members FIRST.

    • @Minty_Meeo
      @Minty_Meeo 11 місяців тому +23

      Dude Jason was driving me nuts in this episode by doing the complete opposite of this!

    • @hampus23
      @hampus23 Місяць тому

      Exactly

  • @victotronics
    @victotronics 11 місяців тому +94

    Next episode: what are cachelines and why do want to align for them?

    • @alexvergara2491
      @alexvergara2491 11 місяців тому +2

      BEST comment and idea

    • @dmitriidemenev5258
      @dmitriidemenev5258 11 місяців тому +11

      2nd next episode: what is endianness or byte order. Also, the monstrosity of the past - mixed-endianness.

  • @stephenhowe4107
    @stephenhowe4107 11 місяців тому +31

    1. I generally arrange largest to smallest members in a struct. That means that 1-byte padding after the i would not occur.
    2. But there is other padding as well. There is often padding at the end of the struct. This is so, if you have an array of this struct, the next item will start on the proper boundary.

    • @stephenhowe4107
      @stephenhowe4107 11 місяців тому +1

      By largest to smallest members, I mean by type. So largest type first, smallest type last.

  • @Christosg8
    @Christosg8 11 місяців тому +9

    This was a nice opportunity to talk about bitfields as well and how the behave under alignment. We use them a lot at work (these 1-bit bitfields are a pain) and I've been bitten before due to alignment requirements of bitfields...

  • @bernardmayeur3952
    @bernardmayeur3952 11 місяців тому +11

    @Jason, why not start with the biggest type first? By starting with the biggest you always have the most optimal struct size right? 8+4+4+1+1+1 (+1 for alignment here) = 20 or 24

    • @bernardmayeur3952
      @bernardmayeur3952 11 місяців тому

      Never mind. Yours will also work. I was thinking that packing biggest first was a best practice for cache. Seems both way works

    • @HinaraT
      @HinaraT 11 місяців тому +3

      Yes but your solution prevent the padded warning for clang on the fields and instead put it on the struct

  • @snbv5real
    @snbv5real 11 місяців тому +8

    I don't usually use packed structs... *if I'm in control of the struct definition*. I usually manually pack the struct and static_assert() the size is what I manually calculated, this comes up a *lot* in Vulkan/OpenGL/ anything that touches GLSL, where default alignment rules are not like C++, and default to *large* padding amounts. In the case of OpenGL/Vulkan code, I will often actually manually insert padding variables because those rules don't line up with C++. For a lot of other uses, in particular *data serialization*, I'm forced to pack structures, because the structures are *defined* to be packed. There's a data format I have to use a lot, that *must be packed* when over the wire or stored via io, otherwise none of the functions de-serializing it will work. It's sad, because the structure *could* have been re-arranged such that packing was unnecessary, though the order of elements may not have been as human friendly.

  • @danielkatzav9971
    @danielkatzav9971 11 місяців тому +1

    Hey Jason ! I was actually waiting for a good video from you about this matter
    I do use packed struct - but as part of multiple embedded environments.
    I would be happy share my thoughts and takes and learn from that experience.

  • @LeDabe
    @LeDabe 11 місяців тому +5

    A quick fix is simply to start the struct with the largest type and continue with the smallest type. If we assume every type is a power of two in size, you can prove that you will have the less padding this way.

    • @LeDabe
      @LeDabe 11 місяців тому

      so does sorting the values from smallest to largest AFAIK

    • @WndSks
      @WndSks 11 місяців тому +2

      X86 can access the first x bytes in the struct faster than later bytes, also sort by how important the member is. (I don't remember what x is, 256 perhaps)

    • @MirralisDias
      @MirralisDias 11 місяців тому

      ​@@LeDabeI was expecting him to place the long long first, then the ints, and then the chars. I can't test it now, but in this way would it be possible to get 19 bytes?

    • @loicballeydier5546
      @loicballeydier5546 11 місяців тому

      @@MirralisDias i think you have to be aligned on 8 bytes (64bit OS) so it will be also 24 bytes

    • @AlfredoCorrea
      @AlfredoCorrea 11 місяців тому

      it seems to me that the general rule is that you can do small to large or large to small or any “std::rotate” :) of that. no?

  • @not_ever
    @not_ever 11 місяців тому +3

    Clang tidy will also detect padding issues if you can’t use Wpadded. Apologies if this was said I’m in a noisy environment

  • @Omnifarious0
    @Omnifarious0 11 місяців тому +2

    I've always put the smallest ones at the end. I'm not sure which is better in general. If you have them in an array of course, the struct has to be padded out to the correct alignment for the most constrained element in it to be on the right boundary, and so I think that negates any advantage for ascending vs. descending size order.

  • @Tyranisaur
    @Tyranisaur 11 місяців тому +21

    I think I'm using packed structs correctly, by not using it 😃

    • @ABaumstumpf
      @ABaumstumpf 11 місяців тому +1

      Same thing here :)

  • @avramlevitter6150
    @avramlevitter6150 11 місяців тому +2

    This may seem like a minor issue, and it could be in a lot of projects (how many instances are you going to have of this particular struct?) but in some applications you might have millions of these you need to allocate and iterate, and in those the order of the fields is absolutely critical. And saving space isn't always better- due to the way memory access works, you might want to put members that are accessed together frequently next to each other even though it means the compiler injects a lot of padding.

  • @caiovinicius7970
    @caiovinicius7970 7 місяців тому

    to avoid padded warnings you can introduce "char padding" members in your struct, to fill the gaps. this also allow for more easy knowing of were you can put new members in this struct wo breaking the alignment

  • @Денис-ь8ь7с
    @Денис-ь8ь7с 6 місяців тому

    I believe that when you arrange in largest-to-smallest way, you always get the best result. Because every smaller-to-larger pair causes padding and larger-to-smaller (or same-to-same) not. And end-of-the-struct padding with proper ordering consequently will be at least not bigger than total padding for any other member permutation case.

  • @FWindowsUseGaruda
    @FWindowsUseGaruda 11 місяців тому +1

    Thank you, this was an excellent explanation.

  • @tigrankato
    @tigrankato 11 місяців тому +3

    Is there any way to tell the compiler to automatically reorder struct members for the smallest possible size?

    • @ZHDINC
      @ZHDINC 11 місяців тому

      I'm curious about this as well. It seems like it'd be something trivial for the compiler to optimize. I mean, I certainly don't care if the compiler re-arranged the ordering of class members so they'll use less space.

    • @imtootired4122
      @imtootired4122 11 місяців тому +2

      C++23 requires that the order of the members in memory to match the order in the definition. Earlier standards were a bit looser and allowed members with different access control to be re-ordered.

    • @cppweekly
      @cppweekly  11 місяців тому +1

      @@imtootired4122 But no one did

  • @PGJVids
    @PGJVids 11 місяців тому +1

    Arranging the values from largest size to smallest size, then no padding would ever be used between values (64bit at the top, followed by 32bit, then 16bit, then 8bit). There could still be padding at the end when you have the structs in an array, to make sure the alignment of the next struct is correct.
    All this about alignment and padding is something i've had to deal with a bit while learning to use the Vulkan API, since the GPU doesn't handle it for you at all.

  • @badassopenpolling
    @badassopenpolling 11 місяців тому +1

    Add data memebers in descending order of their size- then you will get optimal padding

  • @anon_y_mousse
    @anon_y_mousse 10 місяців тому

    Your manipulation of tabular data had me wondering if CE could do blockwise selections, since it has a limited subset of Vim keybindings, and apparently it can. So, just a tiny tip for you for next time you want to demonstrate editing a table of data, such as comments at the end of every line, you can just hit ctrl+v and select the entire block at once then x or d to delete it. You just need to enable Vim keybindings in the interface.

  • @LesleyLai
    @LesleyLai 11 місяців тому +3

    Anyone has an idea about why do you ever want to use std::alignment_of and use std::aligned_storage instead of alignof and alignas?

    • @friedkeenan
      @friedkeenan 11 місяців тому +1

      You don't want to use them, as I understand it. std::alignment_of was created before `alignof` existed, but now `alignof` supersedes it in all ways, though it still works fine. std::aligned_storage in fact can't alias for other objects like an alignas(T) std::byte[sizeof(T)] could, so using std::aligned_storage is probably in fact an error

    • @bryce.ferenczi
      @bryce.ferenczi 11 місяців тому +3

      You probabbly wouldn't want to use std::aligned_storage in greenfield code now since its depricated in cpp23+

    • @bryce.ferenczi
      @bryce.ferenczi 11 місяців тому +1

      You probabbly wouldn't want to use std::aligned_storage in greenfield code now since its depricated in cpp23+

  • @roblauer7568
    @roblauer7568 11 місяців тому

    I might be wrong, but ~5:54 in line 20 the comment says that the char is in byte 25. Isn't it 24?
    char a; // [0, 1) (1)
    // padding // [1, 4) (3)
    int i; // [4, 8) (4)
    char b; // [8, 9) (1)
    // padding // [9, 12) (3)
    int j; // [12, 16) (4)
    long long l; // [16, 24) (8)
    char c; // [24, 25) (1)

    • @cppweekly
      @cppweekly  11 місяців тому +1

      I probably did get something wrong, yeah.

    • @roblauer7568
      @roblauer7568 11 місяців тому

      @@cppweekly thanks for your reply. I’m shocked that you take the time :O
      Thank you for all the great videos! I learn so much from them.

    • @njagimwaniki4321
      @njagimwaniki4321 6 місяців тому

      @@roblauer7568 Why is the total byte usage here 32 and not 25? Is it because we have to store things in multiples of 8?

  • @seanmacfoy5326
    @seanmacfoy5326 11 місяців тому

    Does the last char not start on byte 24?

  • @nicholaskomsa1777
    @nicholaskomsa1777 7 місяців тому

    padding issues are solved largely by Structures of Arrays programming method, speed increase even more dramatic than worrying about complicated structure concepts.

  • @onaecO
    @onaecO 11 місяців тому

    Any sources to really understand the deep stuff? Why this alignment gives optimization?

  • @friedkeenan
    @friedkeenan 11 місяців тому +1

    Not only can unaligned access be slower, but it can also be simply disallowed by the hardware and cause a crash.

    • @Minty_Meeo
      @Minty_Meeo 11 місяців тому

      PowerPC had really great support for unaligned writes... except for the Wii which shipped with a hardware bug that corrupts unaligned writes to uncached memory! It's usually not worth the risk to invoke the terror of unaligned writes on any platform.

  • @nicholaskomsa1777
    @nicholaskomsa1777 7 місяців тому

    first of all, I'd more than likely be using items like std::uint16_t or std::uint32_t, and then std::size_t. Your code appears depreciated.

    • @cppweekly
      @cppweekly  7 місяців тому

      Not at all - size_t means "native machine size type" this is the correct thing to do for crossplatform code. int32, etc, should be used when I want to specify the exact type right now.

    • @nicholaskomsa1777
      @nicholaskomsa1777 7 місяців тому

      @@cppweekly Rather than using c-style data types that aren't quite so clear about their properties, which you describe in your video. Instead, if you want to clarify them - which is always - you use std data types and constructs. std::size_t is not designed to be portable, it's supposed to be the largest possible unsigned integer-type the system supports. std::size_t is entirely system dependent by design.

  • @宋哲宇-p2n
    @宋哲宇-p2n 11 місяців тому +1

    I'm just curious about if the compiler can do the rearrangement for us automatically, since it's a seemingly simple optimization

    • @cppweekly
      @cppweekly  11 місяців тому +1

      Absolutely not, because it would break ABI compatibility

  • @mytech6779
    @mytech6779 11 місяців тому

    Most of the time address 0x00 is special and not used for normal allocation, thus it is overloaded as the null pointer address. There are exceptions though where derefrencing a pointer to 0x00 is a valid thing to do and not the same as trying to deref a null pointer. Really a lot of systems avoid the whole thing with special handling of 0x00 page in the virtual address mapping.
    (boilerplate disclaimer: If either special alignment or access to 0x00 really matters then double check the manuals for your system; hardware and OS)
    Though I can see how 0x00 being considered special could lead to assumptions that create an off by one alignment error.

  • @pandacongolais
    @pandacongolais 11 місяців тому +1

    The additional episodes list grows fast in the other comment.
    If not already done, please add _packing_ (+ endianess)
    I'm using this a lot in an embedded context. But I'm sure I'm missing some concepts, and you are certainly one of the most skilled to teach us !

    • @cppweekly
      @cppweekly  11 місяців тому +2

      It is, in fact, impossible to keep track of the topic requests in the video comments! I have a tracker here where you can add your request so it won't get lost - github.com/lefticus/cpp_weekly/issues/

    • @pandacongolais
      @pandacongolais 11 місяців тому

      @@cppweekly Done
      Maybe you should give this link in every video you publish, in the description or a pined comment.
      Anyway, a lot of thanks for your time and for making us less dumb !

  • @lysy-zn2gg
    @lysy-zn2gg 11 місяців тому

    I think you should only care in critical parts of code like Vertex structs in game development etc

  • @milasudril
    @milasudril 11 місяців тому

    Isn't packed structs for interfacing with odd-sized binary formats? The question is then if you should save values to an unpacked version of the same struct when loading data, or keep it as is despite a potentially slower access to individual fields.

  • @dmitriidemenev5258
    @dmitriidemenev5258 11 місяців тому

    1:49 It's important to note that alignment of types is architecture dependent.
    7:03 Such behavior was inherited from C and isn't that great because the order of fields that is useful for humans isn't particularly good for the compiler. I find that the Rust's behavior is better because there the order of fields in the binary representation is decided by the compiler unless the programmer opts into doing this manually.
    There is also a very important overlooked topic - trailing padding with nested structs. It can highlight the importance of compile-time reflection and maybe you can demonstrate a workaround with boost/pfr.

  • @schmide
    @schmide 11 місяців тому +3

    Must is not correct. x86 is a byte addressable machine. There is no alignment restrictions on basic types. (char, short, int, float, double, long long). If they do cross their alignment, there is at most a load delay if the cpu crosses a cache line boundary. (64 bytes) In previous generations (sandy bridge and before) vector instructions (SSE/AVX) would fault if they were not aligned to a 128bit boundary, this has been relaxed since then and they will not fault as long as they are aligned to a dword. Regardless of all this it is good practice to align data. It matters on older hardware and ARM/RISC/GPU can often require greater alignment. As for what the user has to consider. For modern OSs, all heap allocations are cache aligned at 64bytes, stack primitives are often aligned as well, so if you are doing vector processing you need not declare alignment as you did before. As for manual ordering, it is best and easiest to put the largest types first, then the rest will fall into place. If you put smaller primitives first you have to group them to align with the latter elements or padding will be introduced.

  • @mateuslima5102
    @mateuslima5102 11 місяців тому

    i use packed structs to send data via serial protocol, is that correct?

    • @cppweekly
      @cppweekly  11 місяців тому +1

      You've probably got undefined behavior on the other end, if you're using reinterpret_cast or a c-style cast.

  • @AtomicAndi
    @AtomicAndi 11 місяців тому

    visual studio has a new feature to visualize class layout
    apparently I'm not allowed to post links

    • @AtomicAndi
      @AtomicAndi 11 місяців тому

      oh, we are sponsored by CLion ;-)

    • @Minty_Meeo
      @Minty_Meeo 11 місяців тому +1

      UA-cam will block any comments with links to outside websites without exception. I'm not even sure if links to other UA-cam videos always make it through the filter.

    • @AtomicAndi
      @AtomicAndi 11 місяців тому

      @@Minty_Meeo I often comment with links to videos, so that's fine

  • @alpergunes6380
    @alpergunes6380 11 місяців тому +2

    I usually prefer to add explicit padding to the end of my structs to make the wasted space explicit and visible.
    struct S {
    char c;
    char _pad[3];
    int i;
    long long l;
    };

    • @AlfredoCorrea
      @AlfredoCorrea 11 місяців тому +3

      this is ok, but not if you care about aggregate initialization. perhaps named field initialization is a workaround now, I haven’t tried.

    • @alpergunes6380
      @alpergunes6380 11 місяців тому

      @@AlfredoCorrea true

    • @AlfredoCorrea
      @AlfredoCorrea 11 місяців тому

      @@alpergunes6380 Perhaps a hypothetical "hidden" attribute could solve this. `hidden: char _pad[3];`

  • @ysakhno
    @ysakhno 11 місяців тому

    You should have reversed the order of your fields in fact. As a rule of thumb, first should go all the long longs, then all ints, then all short ints (if you have them), then all chars/bytes/bools (if the size of bool is 1 byte). That is if you care about total size of your structs / performance so much. But in most cases this is largely irrelevant, in other words you should only care when you start to see problems.

  • @Yupppi
    @Yupppi 11 місяців тому

    Why doesn't compiler do anything about the padding? I would've intuitively set long long first, then ints and then chars.

  • @rafi1082
    @rafi1082 11 місяців тому +1

    wrapping the struct with "#pragma pack(1)" will make it compile

    • @charlesolson9019
      @charlesolson9019 11 місяців тому +3

      That's a compiler-specific pragma, and I'm guessing it makes the code less efficient. I have done that in network protocol work, where you `reinterpret_cast()` a blob of data into a struct.

    • @bcodetube
      @bcodetube 11 місяців тому

      #pragma pack(push, 1)
      struct X {×××} ;
      #pragma pack(pop)
      This is also useful to read binary files that have well defined headers

  • @jasoncole7711
    @jasoncole7711 11 місяців тому

    Historically I've also sorted stack variables to be largest to smallest size; I don't know whether the compiler is smart enough to rearrange them if declared less than optimally.

    • @cppweekly
      @cppweekly  11 місяців тому +1

      On a modern compiler your stack objects are probably just residing in registers anyhow, or eliminated entirely.
      Your stack variables should be ordered in the way it logically makes sense to use and initialize them, and they should be limited in scope as much as possible usually.

  • @stephenhowe4107
    @stephenhowe4107 11 місяців тому

    I use packed structs but only for structs that are written to disk. If they are not, I use the default packing. Usually this is achieved via #pragma pack.

    • @oracleoftroy
      @oracleoftroy 10 місяців тому

      I would recommend not doing that. It is ok if you know your compiler, your platform, your cpu and don't need any sort of portability (the same computer will read and write the file) and are certain you won't run into alignment, packing or other issues no matter what happens in the future.
      Otherwise, I'd manually read/write the bytes in the proper order with the proper packing for the file. Something like:
      // write 32-bit little endian
      buf[0] = (value >> 0) && 0xFF;
      buf[1] = (value >> 8) && 0xFF;
      buf[2] = (value >> 16) && 0xFF;
      buf[3] = (value >> 24) && 0xFF;
      // read 32-bit little endian
      value = (buf[0]

  • @emilkaczmarek4641
    @emilkaczmarek4641 11 місяців тому

    GCC 13.2 supports -Wpadded (cannot try earlier versions at the moment), but as warnings and is not as informative as clang.

    • @paulfee9839
      @paulfee9839 11 місяців тому

      Testing with compiler explorer, GCC supports -Wpadded at least back to version 4.1.2.

    • @ChristophePiault
      @ChristophePiault 11 місяців тому

      @@paulfee9839 It means that GCC supported -Wpadded before clang initial release. Pure free useless GCC bashing at 7:18.

  • @mgeding
    @mgeding 11 місяців тому

    Absolutely bizarre that there was no mention of the `ailgnof` operator.

  • @jeanclaudevanslamme
    @jeanclaudevanslamme 11 місяців тому

    Are you seriously suggesting that I, someone who is watching C++ educational content, might be making mistakes in C++? >:(

  • @oldlonecoder5843
    @oldlonecoder5843 11 місяців тому

    Wtf? Pack will address exact struct representation of physical memory address for devices and physical address of IO ports, registers, flags....!!
    Nothing to do with c++!!!!!!!! Wtf? Pack is to get out of c++ normal addressing data in memory (natural integer and address boundaries padding)