4 ms·
The x32 ABI is another opportunity for optimization: https://en.wikipedia.org/wiki/X32_ABI https://en.wikipedia.org/wiki/X32_ABI x32 binaries are different fro
by winter_blue 4y ago
The x32 ABI is another opportunity for optimization: https://en.wikipedia.org/wiki/X32_ABI https://en.wikipedia.org/wiki/X32_ABI
x32 binaries are different from x86 programs in that they get access to 64-bit x86 registers, while their pointers are 32-bit.
It would be really cool if some Linux distro provided both x64 and x32 packages for programs not expected to rarely if ever use more than 4 GiB.
Similarly, it would be cool if Chrome and Firefox spawned x32 processes for most web pages, and then only spun up a x64 process when a web page exceeded like 2 GiB of memory usage (and transparently & quickly converted the page over onto the new process).
- mcculley 4y ago> It would be really cool if some Linux distro provided both x64 and x32 packages for programs not expected to rarely if ever use more than 4 GiB. How would one decide which version to use? Wait for the x32 version to crash and try again?
- kevin_thibedeau 4y agoUse the same mechanism for running i386 code under x64.
- chubot 4y agoFWIW I recently tried this on https://www.oilshell.org https://www.oilshell.org, thinking the code would be faster due to better cache locality. There are many pointer rich data structures and they should be packed much more tightly when pointers are 32 bit. It definitely saved memory, but it was slightly slower or the same speed. My guess is that 32-bit performance isn't prioritized on x86-64 by either compiler writers or chip designers, i.e. they optimize for the common case of 64 bit pointers. Definitely interested in other comments / opinions / data on this. I think if it actually sped programs up, you would see many more distros compiling packages for 32-bit. It does save memory, but somehow that's not big enough motivation. Probably because desktops/laptops have 8GB+ RAM these days (enough for most apps), and Linux systems with constrained memory are probably already 32-bit. And because real memory hogs like browsers probably want to address more than 4GB. Though to be honest I think a multi-process browser with a 4GB-per-origin limit sounds like a very good idea! Another issue is probably shared libs. Linux distros are pretty tightly coupled in this respect, and if core system programs like the compiler want to use 64-bit libc, etc. then it's economical for other programs to also be 64-bit as well. Otherwise the dependency management problem for shared libs becomes more annoying.
- haimez 4y ago> It definitely saved memory, but it was slightly slower or the same speed. My guess is that 32-bit performance isn't prioritized on x86-64 by either compiler writers or chip designers, i.e. they optimize for the common case of 64 bit pointers. That may be partially to blame, but depending on your implementation (was this x32 or actual compressed pointers?) the overhead of “decompressing” each pointer dereference may have been a significant factor. For null-safe languages like Java or JavaScript you already have null checks and may have GC barrier checks on most dereferences, so the extra costs are already expected- but adding a shift and/or constant add to each access is going to be a more noticeable cost compared to plain accesses in native code.
- chubot 4y agoThis is dirt simple native C++ code compiled with -m32, with structs and pointers (which happens to be generated, but that doesn't matter here) I would expect that dereferencing a 32-bit pointer on x86-64 has zero overhead compared to dereferencing a 64-bit pointer (though I didn't test it). The only way I can see it being slower in the context of a real program is if the base address is an operand that has to be loaded from memory. But it shouldn't be because it's a 32-bit address space and it should be handled by the VMM, i.e. the base address is constant for the entire process.
- bremac 4y ago-m32 doesn't target the x32 ABI though, it targets x86. Per the manual: > The -m32 option sets int, long, and pointer types to 32 bits, and generates code that runs on any i386 system. That restricts you to the smaller register set, removes all amd64 instructions, and uses the x86 ABI (on Linux, parameters are passed on the stack instead of in registers.) I suspect that this is why your program became slower, as opposed to using 32-bit pointers.
- chubot 4y agoHm so then how do you compile for x32 and have 32-bit pointers without the other restrictions? It sounds like x32 is poorly supported by Linux and x86 would be better supported.
- scottlamb 4y agoAFAICT, x32 is basically dead. I'm gonna go out on very thin ice and disagree with a Donald Knuth quote: > It is absolutely idiotic to have 64-bit pointers when I compile a program that uses less than 4 gigabytes of RAM. When such pointer values appear inside a struct, they not only waste half the memory, they effectively throw away half of the cache. In general if shrinking the pointer size is a huge optimization, your data structures have too many pointers in them. Learning Rust was a turning point for me here. The borrow checker encouraged me to get rid of references to parents or siblings. In general, I ended up passing parent references on the program stack and having sibling references (e.g. in graph structures) via indices into a slab, which can be u32 rather than pointer-sized. You could also write a SmallVec/SmallString that uses u32 for len/capacity rather than usize, which would be a nice savings if you have lots of them. And/or move the len/capacity inside the heap allocation, so empty/unallocated vecs/strings don't have those fields at all. Or do what Rust's smallvec crate does and C++'s std::string's "small string optimization" does: carve out the space for them but use it for actual object storage until it overflows and you need to move to the heap. If you're working in a language that doesn't have value objects (that is, all user-defined structures are implicitly boxed), my sympathy, you will just end up with a bunch of extra pointers, which is why this kind of optimization is important for Java and JS. I consider lack of value objects to be a significant design flaw in a language.
- dist1ll 4y agoI'm not sure I understand your point. First you say: > In general if shrinking the pointer size is a huge optimization, your data structures have too many pointers in them. And then you go on to say > having sibling references (e.g. in graph structures) via indices into a slab, which can be u32 rather than pointer-sized. What you're describing is literally pointer compression. You're using offset indices that point to a compressed/sufficiently small address space.
- scottlamb 4y ago> What you're describing is literally pointer compression. You're using offset indices that point to a compressed/sufficiently small address space. True, but it's finer-grained than a global switch. I can decide that I will have at most 2^32-1 objects in this particular graph/slab without giving up the ability for my program to address more than 4 GiB of RAM in total as it would with the x32 ABI. Also, this trick is only a small part of reducing pointer storage. I mentioned passing stuff in via the stack vs keeping it in the data structure. I forgot to mention: use vecs rather than e.g. linked lists, open addressing hash maps rather than chaining, btrees rather than red/black trees, etc. This is also stuff that Rust's std library encourages. btw: of course none of the techniques I mention are unknown to Donald Knuth. He's likely either wanting to get programs written by others to run more quickly or trying to optimize well beyond what I'd care to do. I'd rather split the difference, basically: decrease pointer density a fair bit and keep the pointers at 64-bit.
- aseipp 4y agoA big problem is that it requires an entire ABI and operating system support to handle this case, when the alternative is... just use a custom heap in your own program. You're describing an insane level of engineering for something that mostly is hidden and can be handled just as effectively by the applications. Like, if you're in the weird case where A) pointer chasing is killing your performance and blowing your cache and B) your working set fits in 4GB, you can just fix this yourself with your own custom allocator to handle that. You'll still be pointer chasing, mind you, just half as much (and there's the thing: you could very well be much better off reducing the chasing you're doing versus mitigating it this way.) In reality superscalar OOO processors hide miss latency very effectively helping mitigate the problems of A, and B isn't always clear from the start. So here we are. Don't get me wrong, I want more efficient programs, and centralizing some complexity like ABI is a good thing. But in practice x32 was a massive technological investment for something that mostly didn't matter, that people mostly didn't need, and whose benefits can be gotten much more easily and directly in a lot of cases.
- chubot 4y agoI agree that the x32 ABI is probably a technological dead end, but I don't agree with "just" use a custom heap. I tried in C++ and it's not easy. You lose a lot of type safety and you have to basically rewrite every line of your program. I mentioned the Zig compiler here: https://news.ycombinator.com/item?id=33797368 https://news.ycombinator.com/item?id=33797368 Another program I remember that uses small integers instead of pointers is the Sorbet type checker (not coincidentally it also has a fine-grained AST code representation): https://blog.nelhage.com/post/why-sorbet-is-fast/ https://blog.nelhage.com/post/why-sorbet-is-fast/ I peeked at the code awhile back, and it's not really idiomatic C++. It's obviously doable but you have to architect the program from the start for it -- it's not feasible to retrofit this technique onto existing software.