The byte order fallacy
commandcenter.blogspot.com
commandcenter.blogspot.com
But Plan9 is not a system known for its graphics, and I think performance would seriously suffer if everyone had to program like that. Being able to load a pixel as an int is the reason 32-bit RGB is used more often as a pixel format than 24-bit.
Of course it might not matter as much these days, GCC and LLVM can optimize his code sequences into bswap instructions automatically. And SIMD/shader code don't have endian portability problems I know of, if only because SIMD is already not portable.
Evidence please.
x86 practically offers it for free in newer architectures (Sandy Bridge, Ivy Bridge and Bulldozer).[2]
[1] https://developer.apple.com/hardwaredrivers/ve/g5.html
[2] http://agner.org/optimize/instruction_tables.pdf (check MOVDQU timings)
The point made by the author addresses this issue from a different angle.
As the author say, programmers should always write endianess neutral code unless it is impossible which is generally at the interfaces, where data is read and written (I/O) by the program. If the code is correctly and intelligently optimized so that marshaling is done once, then the byte swapping may generally be expected to be a low frequency operation. In this case the most simple and portable code should be favored.
Trying to optimize this operation by word read and byte swapping provides an insignificant optimization with a higher cost on code portability and maintainability. The author is right on this.
Though it is also true that in some cases, the operation frequency is very high (i.e. reading million pixel values of an image). For these use cases, the programming overhead of using highly optimized code is perfectly justified. But then don't use half backed optimizations. Try to align data on words (twice faster), read by word (four time faster) and use byte swapping machine instruction available on the target CPU instead of the proposed shifts and bit masks.
My opinion is that good languages should provide optimized data marshaling functions in their library so that the code can be optimal and portable at the same time.
#include <stdint.h>
uint32_t load_uint32_be(uint8_t* p) { return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; }
uint32_t load_uint32_le(uint8_t* p) { return (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]; }
===============
gcc -O3 -fomit-frame-pointer -S bo.c
===============
load_uint32_be: movl 4(%esp), %edx
movzbl (%edx), %eax
movzbl 1(%edx), %ecx
sall $24, %eax
sall $16, %ecx
orl %ecx, %eax
movzbl 3(%edx), %ecx
movzbl 2(%edx), %edx
orl %ecx, %eax
sall $8, %edx
orl %edx, %eax
ret
load_uint32_le:
movl 4(%esp), %edx movzbl 3(%edx), %eax
movzbl 2(%edx), %ecx
sall $24, %eax
sall $16, %ecx
orl %ecx, %eax
movzbl (%edx), %ecx
movzbl 1(%edx), %edx
orl %ecx, %eax
sall $8, %edx
orl %edx, %eax
ret
GCC doesn't merge the 4 byte-level reads into one 32-bit read. Thus, it does cause some performance penalty. The true impact is probably quite low, but it does exist on x86.It is true however, that GCC will take a series of bit ops and produce a 'bswap' instruction on x86, but that requires a full 32-bit word to start with.
No: RGBA.
I agree slightly less with "computer's byte order doesn't matter", differentiated from peripheral and data stream byte order... that's the same friggin thing. It matters that you treat your inputs and outputs correctly, and how you do that depends on your computer's byte order, so they computer's byte order does matter. Just not so much during the data processing stage.
But mostly, I'm just saddened that every post about C now has a "only people who do [X thing that requires C] do that, and you're probably not one of them, so you should do that!" Maybe there's just a huge disconnect between people-who-blog and people-who-write-low-level-code, but most of the software guys I know have worked professionally on microcontrollers, DSPs, operating systems, or compilers within the last 5 years, and I'm working on a compiler for a DSP right now (and I expect byte-order to matter).
Also, it's been 5 years since I had to deal with it, but I remember endian mattered for some image file formats, and also for blitting to the screen for Mac vs PC.
- The byte order / endianness
- The alignment of variables
- The exact width of variables (32-bits, 64-bits)
"struct" is great for what it was designed for, storing your internal data structures in a way efficient for the machine.
But everyone abuses structs and tries to read external data sources e.g. files using them. They might hack it to work on their own machine, then as soon as a machine of the other endianness comes along, hacks and #ifdefs appear, then machines with ints of different widths come along....
Of course these people are using structs "wrong", like the author of the article suggests. But nevertheless, the fact that people are using structs "wrong" suggests there is a need for something that provides what people are trying to use structs for.
I think it would be easier for the programmer to use a language feature than a library; the resulting code would be easier to read. I'm thinking how regular expressions are easier to use in Perl than they are in Java because they're part of the language, or how maps are easier to use in scripting languages than say Map in Java because they're part of the language.
If you had to have some e.g. string or external file describing the syntax of the hardware-independent struct, and then some calls like "read_entity(void structure, char fieldname)", it would all get nasty - you're going to have strings in the code which can't be checked at compile-time, you're going to be doing type casting which can't be checked at compile-time, and so on.
But a code generation system could be an option - you define the structure in a file in a certain syntax, and then code is generated with the right types and attribute names being visible to the compiler.
But I still think being part of the language would just be simpler for the user, and I don't consider this to be some obscure feature which would dilute the purity of the language by its introduction; binary file formats and protocols are here to stay.
P.S. Yes Google Protocol Buffers could well be the thing I've been searching for since I first saw the C struct many years ago.
... of course, now that I write that I realize a language feature could actually provide all of that, too.
* define the struct with all elements
* define a string for the binary representation
* call fpack/funpack with the string and all the struct elements as parameters...
Unfortunately, fixing this either requires some kind of black X-macro [2] magic or another template language used to write the specification and to generate the three above-mentioned representations from it...
Surely this could be handled via simple syntactic extensions to the struct specification (with everything wrapped into an ungodly macro from hell) in order to define the mapping between the struct itself and libpack's format string, no?
It might be possible to construct a macro that creates both the struct and the format string, though.
Don't you only need the (generated) format string? Ideally, the macro could generate some wrapper function of some sort as well, which would unpack, fill and return an instance of the struct.
I don't think there's a need for a struct-equivalent, I think there's a need for a struct loader with these capabilities, it would only be in charge of packing and unpacking but would shove everything in a struct.
Basically, Erlang's bit syntax for C (thought the bit syntax unpack to locals, not structs):
<< Foo:16/little, Bar:12/signed, Baz:4 >> = Bin.
(default type specifications are integer, unsigned and big-endian, between the : and / is the size of the data in "units", where Unit defaults to 8 bits for the binary type and 1 bit for integers, floats and strings).Doesn't natively do alignment though, the developer has to pad on his own.
Python's `struct` module is similar[0]: http://docs.python.org/library/struct.html although the format string is basically unreadable and I believe it's absolutely terrible at decoding non-standard sizes (e.g. an int stored on 3 bits)
[0] libpack[1] for C, Perl also has this[2] which was probably the inspiration for Python
However, the other 2 issues can be tackled. The first by using a packed struct ( __attribute__((__packed__)) in gcc) and the second by using stdint.h.
I disagree; if you have "littleendian int32 myfield" for example, every time you reference myfield on a big-endian architecture, the compiler inserts the necessary byte manipulation code, just like the guy does manually in the original post.
typedef union {
uint8_t bytes[sizeof(uint64_t)];
uint64_t native; /* alignment hint */
} uint64le_t;
which I use in structs of my on-disk data structures. struct ondisk_range
{
uint64le_t start;
uint64le_t end;
};
Then for actually using that data: struct range
{
uint64_t start;
uint64_t end;
};
The conversion functions basically just call functions with these prototypes on the 2 fields: uint64_t le64_to_cpu(uint64le_t le_val);
uint64le_t cpu_to_le64(uint64_t val);
Which internally just read out the byte array and turn it into an integer and vice versa.(I also have static assertions for the expected size following each ondisk struct)
It'd be nicer to codify this as a DSL or something, but C's macro system really isn't up to the job.
And at least in that case, the code would be more readable.
But, as the compiler knows more about what's going on (it's not just parsing and compiling a general expression with ORs and shifts) then it could well be faster (e.g. if there were a CPU instruction to do this, then it could be used, etc.).
http://golang.org/pkg/encoding/binary/#Read
I've been working on some code to parse the shapefile format, which specifies some fields in big-endian and some in little (I have no idea why), but the binary package has made it really easy to deal with.
What are some of the assumptions which Linux makes? For one, that there is a 32-bit type available to the compiler. For just about all modern CPU architectures where you might want to run Linux, this is true. This means that we can define a typedef for __u32, and it means that we can declare C structures where we can use a structure layout that represents the on-the-wire or on-the-disk format without needing to do a pull the bytes, one at a time, off the wire decoding stream. It also means that the on-the-wire or on-disk structures can be designed to be such that integers can be well aligned such that on all modern architectures such that we don't have to worry about unaligned 32-bit or 64-bit accesses.
And it's not just Linux which does this. The TCP/IP headers are designed the same way, and I guarantee you that networking code that might need to work at 10 Gbps isn't pulling off the IP headers one byte at a time and shifting them 8 bits at a time, to decode the IP header fields. No, they're dropping the incoming packet on an aligned buffer, and then using direct access to the structures using primitives such as htonl(). (It also means that at least for the forseeable future, CPU architectures will be influenced by the implementation and design choices of such minor technologies such as TCP/IP and the Linux kernel, so it's a fair bet that no matter what, there will always be a native 32-bit type, for which 4-byte aligned access will be fast.)
The original TCP/IP designers and implementors knew what they were doing, and having worked with some of them, I have at least as much respect, if not more so, than Rob Pike...
True. But you might consider using inttypes.h which defines some pretty useful things like uint32_t (an unsigned 32 bit wide integer for example).
- "may be a little faster on little-endian machines, but not much, and it's slower on big-endian machines."
In fact swapping the byte order is _one_ CPU instruction. You can for example use some inline assembly to optimize your code. (If your compiler fails to recognize this pattern.)
uint32_t byte_swap( uint32_t x )
{
asm( "bswap %0"
: "=g"(x)
: "0"(x)
);
return x;
}
Just my two cents...For details on the syntax: http://wiki.osdev.org/Inline_Assembly#Clobbered_Registers_Li...
That's one machine instruction, I'm pretty sure it's more than one microcode instruction ;)
Still, the layout of something like a barrel shifter (e.g. http://www.erc.msstate.edu/mpl/distributions/scmos/images/bs... , from a casual search) takes its space on die, much like an adder or multiplier. It's all wires and switches.
PowerPC has byte-reversing load and store instructions but lacks an instruction to reverse a register.
asm ("bswap %0" : "+r"(x));
That said, since version 4.5, gcc recognises the typical byte-swapping pattern and uses the appropriate CPU instruction.Tested new code on my x86 box and it worked. Then just committed to sourceforge CVS and told the rest of the world to test. It worked. My code looked a lot like Rob's.
It doesn't really matter much how the possible byte order swap is done: what matters that these ifdefs aren't littered around the code and byte-order swapping is limited to the lowest level where data is actually read from an external source.
I would personally go with his byte array reads as it's less confusing but I would still wrap the functionality inside inlined functions like these:
uint32_t inline read_be32 (void*);
uint32_t inline read_le32 (void*);
And then use these whenever reading 32-bit integers from big-endian or little-endian data source.Whenever I see code that asks what the native byte order is, the odds are about a hundred to one the code is either wrong or misguided.
https://github.com/alexchamberlain/byte-order/commit/b804361...
Nice piece, clearing up a cobweb in a poorly lighted corner. And teaches (with code example) what one really needs to know about handling byte order in data streams.
At -O9, the compiler optimizes a masks-and-shifts swap of a uint64_t into a bswapq instruction identical to the one emitted by the GCC-specific __builtin_bswap64; this can be coupled with an initial memcpy into a temporary uint64_t. Loading individual bytes and shifting them in emits a pile of instructions that take up 16 times as much code space and ~35% runtime penalty (2.7 s versus 2 s). This is measured in a loop decoding a big-endian integer into a native uint64_t and writing it to a volatile extern uint64_t global, 2^30 iterations, function called through a function pointer.
Aligned versus unaligned pointers seem to make no real difference on this CPU, using a static __attribute__((aligned(8))) uint8_t[16] and offsets of 0 (aligned) and 5 (unaligned) from the start of the array.
I also tried a function with the explicit cast-shift-or that uses an initial memcpy into a local uint8_t[8] in case the compiler was doing something strange with regard to memory read fault ordering as compared to the explicit memcpy in the two bswapq-generating versions. This resulted in some very "interesting" code that shoves the local array into a register and then very roughly masks and shifts all the bits around, at about a 100% penalty from the bswapq functions. :-(
If anyone's interested in the details, reply and I'll try to put them somewhere accessible, though it may take a little while.
> The byte order of the computer doesn't matter much at all except to compiler writers and the like
Binary protocol parsing is one area that relies heavily on byte ordering, struct packaging and alignment. Tangentially, binary file parsing that is optimized for speed will have the same dependency. In fact, anything that deals with fast processing of the off-the-wire data will want to know about the byte order.
Looking up a 16-bit int rather than 2 chars, and outputting as a 32-bit int rather than 4 chars yields a nice performance boost at the cost of possibly not being portable for some more esoteric architectures that don't have a 16 and 32-bit unsigned int type.
So while he's right that 99% of the time you shouldn't be fiddling with byte order, it still pays to know how to wield such a tool, and it's most definitely not just for compiler writers.
That said, use Rob's portable approach anytime you don't have a compelling reason not to, if only to not have to worry about alignment and portability. Doing otherwise is premature optimization and a maintenance headache.
#define _BSD_SOURCE /* See feature_test_macros(7) */
#include <endian.h>
uint16_t htobe16(uint16_t host_16bits);
uint16_t htole16(uint16_t host_16bits);
uint16_t be16toh(uint16_t big_endian_16bits);
uint16_t le16toh(uint16_t little_endian_16bits);
uint32_t htobe32(uint32_t host_32bits);
uint32_t htole32(uint32_t host_32bits);
uint32_t be32toh(uint32_t big_endian_32bits);
uint32_t le32toh(uint32_t little_endian_32bits);
uint64_t htobe64(uint64_t host_64bits);
uint64_t htole64(uint64_t host_64bits);
uint64_t be64toh(uint64_t big_endian_64bits);
uint64_t le64toh(uint64_t little_endian_64bits); /usr/include/endian.h memcpy(&i, data, sizeof(i));
i = le32toh(i); // Or whichever function is correct.
This is easier to read and requires less smarts from the compiler to do the right thing efficiency-wise.Where would you get the value of 'i' if you didn't have 'data' and what would you do with the value read from 'data' if you didn't have 'i' or some equivalent?
edit: I mention this case as it covers around 90% of the cases I've seen on a quick check over a codebase I'm working with (Irrlicht). Swapping endian is nearly always done after reading in the data from a file-stream.
> Let's say your data stream has a little-endian-encoded 32-bit integer. Here's how to extract it (assuming unsigned bytes):
i = (data[0]<<0) | (data[1]<<8) | (data[2]<<16) | (data[3]<<24);
Wait, if byte order doesn't matter, why do I need to do byte-level array lookups when i'm processing a stream of integers? Oh yeah, because byte order does matter. If byte order wouldn't matter (say, if all computers were 32-bit, had the same byte order and the same endianness), I could just cast the stream to int* and be done with it. I can't, because of byte order. It matters.Whether you deal with it using byte-array lookups and math or #ifdefs and bitmasks, well, whatever rocks your boat man! Good that you're taking it into account, because byte order matters!
The byte order of the input data obviously matters, and nothing you've said here disagrees with anything he wrote.
I've sat and watch C++ compile for 5 hours... Compile time performance is important too!
i = *((int*)data);
#ifdef BIG_ENDIAN
/* swap the bytes */
i = ((i&0xFF)<<24) | (((i>>8)&0xFF)<<16) | (((i>>16)&0xFF)<<8) | (((i>>24)&0xFF)<<0);
#endif
This should use uint32_t, it is the best way of getting a platform independent unsigned 32-bit integer, which is what you want here.It's more code.
Couple more lines of C, yes. No more at the machine level.
It assumes integers are addressible at any byte offset; on some machines that's not true.
Not sure about this one...
It depends on integers being 32 bits long, or requires more #ifdefs to pick a 32-bit integer type.
This is caused by bad code - see above.
It may be a little faster on little-endian machines, but not much, and it's slower on big-endian machines.
It is faster on a LE machine, but not slower on a BE machine - the same code can be used and it's a compile time #ifdef.
If you're using a little-endian machine when you write this, there's no way to test the big-endian code.
Test on a BE machine?
It swaps the bytes, a sure sign of trouble (see below).
No actual facts here...
As pointed out by another commentor, this can be optimised out by the compiler on many platforms.
No modern architecture can access arbitrarily aligned words in memory directly (presence of caches modifies things slightly, but shifts the problem from data bus width to cache line width as unaligned word can still span two cache lines). There are generally two solutions to this: disallow that at CPU level (and handle that by raising SIGBUS), emulate it in hardware by doing two memory accesses for one load or store (which involves significant additional complexity), Intel invented third solution in i386: OS can select between these two behaviors.
1) Even if compilers are not able to optimize manual conversion of integer to/from discrete bytes into same code as word sized access with optional byte order swap, it's mostly irrelevant, as there aren't going to be any significant difference in performance between one four byte access and four one byte accesses (as in both cases you end up with same number of actual memory transactions, which is the slow part, due to caches)
2) when you are handling portable binary representation of something, it's always connected to some IO, which is slow already so any performance boost that you get from microoptimalization like this is completely negligible.
I tend to just hand write few lines of C to pack/unpack integers explicitly when needed as it seems to me as the most productive thing you can do.
By the way all the big endian <-> little endian functions you propose boil down to two implementations for each size of operand: no-op and mirroring of all bytes, both of which are mostly trivial.
What is really missing is portable and efficient way to encode floating point numbers, as there is no portable way to find out their endianity and in floating point case it's more complex than just big vs. little endian.
That seems like a good idea. Not for -all- the reasons he mentions, but for the simple reason that it's one code path so easier to code and easier to get right.