Nathan Froyd: packing structures for fun and profit |
When the DOM bindings code first started generating information for the JavaScript JIT about getters and setters, the generated information was rather sparse. JSJitInfo, the container for such information, looked like this:
struct JSJitInfo { JSJitPropertyOp op; uint32_t protoID; uint32_t depth; bool isInfallible; bool isConstant; };
On a 32-bit platform, sizeof(JSJitInfo) was 16. You could shrink some of the fields, and even pack things into bitfields, but there weren’t that many getters and setters, so it probably wasn’t worth it.
Of course, nothing ever gets smaller: the bindings code started generating these structures for methods, the amount of information the bindings code wanted to communicate to the JIT expanded, and some of the parallel JS work started using the structure for its own purposes. And a bunch more interfaces got converted to WebIDL, so many more of these “little” structures were generated. Eventually, we wound up with:
struct JSJitInfo { enum OpType { ... }; enum AliasSet { ... }; union { JSJitGetterOp getter; JSJitSetterOp setter; JSJitMethodOp method; }; uint32_t protoID; uint32_t depth; OpType type; bool isInfallible; bool isMovable; AliasSet aliasSet; bool isInSlot; size_t slotIndex; JSValueType returnType; const ArgType* const argTypes; JSParallelNative parallelNative; };
This structure has several issues:
This definition weighed in at 44 bytes on a 32-bit system. With 7k+ of these being generated by the bindings code, and more being added every release cycle, now seemed like a worthwhile time to slim these structures down.
This work has gone on in bug 952777, bug 960653, and yet-to-be-landed bug 960109. After all of those bugs land, we’ll have something that looks like:
struct JSJitInfo { union { JSJitGetterOp getter; JSJitSetterOp setter; JSJitMethodOp method; JSParallelNative parallelNative; }; uint16_t protoID; uint16_t depth; uint32_t type_ : 4; uint32_t aliasSet_ : 4; uint32_t returnType_ : 8; uint32_t isInfallible : 1; uint32_t isMovable : 1; uint32_t isInSlot : 1; uint32_t isTypedMethod : 1; uint32_t slotIndex : 12; };
Compared to our earlier version, we’ve addressed every complaint:
It’s worth noting that several of these fields could be smaller, but there’s no reason for them to be currently, given that shrinking them wouldn’t make the overall structure smaller.
The final size of the structure is 12 bytes on 32-bit platforms, and 16 bytes on 64-bit platforms. With 7k+ JSJitInfo structures, that means we’ve saved ~220K of space in a 32-bit libxul. For a 32-bit libxul, this is almost 1% of the entire size of libxul, which is an extremely pleasant win for size optimizations. Smaller downloads, less space on your device, and less memory at runtime!
If you’re interested in examining how much space particular structures take up, you can use pahole for Linux or struct_layout for Mac. I’m not aware of any similar program for Windows, though I imagine such a beast does exist. These programs work by examining the debugging information generated by the compiler and displaying the structure layout, along with any “holes” in the structure. pahole will also tell you about cacheline boundaries, so that you can pack frequently-accessed members in the same cacheline to minimize cache misses.
Given the above (and that there have been other wins, somewhat less spectacular in nature), I’ve wondered if it isn’t worth adding some sort of annotations, either in-source or off to the side, about how big we expect certain structures to be or how much internal waste we expect them to have. Then people modifying those structures–and the reviewers as well–would be aware that they were modifying something space-critical, and take appropriate steps to minimize the space increase or even trim things down a bit.
https://blog.mozilla.org/nfroyd/2014/01/17/packing-structures-for-fun-and-profit/
Комментировать | « Пред. запись — К дневнику — След. запись » | Страницы: [1] [Новые] |