[swift-dev] [Discussion] New refcount representation
gparker at apple.com
Wed Mar 16 18:08:53 CDT 2016
> On Mar 16, 2016, at 9:25 AM, Joe Groff <jgroff at apple.com> wrote:
> This sounds awesome. Should we still consider using a non-pointer isa representation on 64 bit platforms? 16 bytes per object header is still kinda big. If we laid out the np-isa bits so that the "side allocation" bit were the MSB, and the strong refcount immediately below it, we could pull the same trick letting the strong refcount overflow into the side allocation bit. Drawbacks would be that we'd need to go back to a global sidetable to reference the overflow allocation, and that on Apple platforms, we'd need to follow whatever non-pointer-isa layout the Objective-C runtime uses, and the exact layout of the bits would have to remain runtime-private and thus not inlineable. If we're running on the assumption that side allocations are rarely needed, then paying for the lock in the rare case might be worth a potentially substantial memory savings.
Packing everything into a single 64-bit field is tricky but might work.
I think OS X and Linux x86_64 is the worst case currently, with 3 low bits and 17 high bits available. Here's what libobjc stores in its isa field on x86_64:
1 bit is nonpointer
1 bit has associated references
1 bit has destructor
44 bits class pointer
6 bits magic for tools
1 bit is weakly referenced
1 bit is deallocating
1 bit has additional refcount in side allocation
8 bits refcount
Some of those bits can be combined or reduced:
is-nonpointer is unnecessary as long as at least one of the bits outside the class pointer is set. There may be binary compatibility problems here, though. (Do existing Swift apps check that bit directly?)
has-associated-references and is-weakly-referenced and has-additional-refcount could be folded into a single has-sidetable bit.
magic is needed for tools like `leaks` to distinguish real objects from non-object allocations. If the isa field is not a simple pointer then there are otherwise too many false objects for `leaks` to be usable. 6 bits is pushing the bare minimum here, but we might be able to trim this by making the tools smarter and more invasive. (For example: if the has-sidetable bit is set then look for a side allocation pointing back to the object. If you don't find one then it's not a real object.) Being able to run `leaks` and `heap` on an unprepared process is an important capability, and I don't think we can afford to cripple it.
refcount can always be trimmed. I don't know what the shape of the "objects whose refcount is ever greater than X" curve looks like.
Given the above, we can claw back enough bits for things like a small unowned refcount and is-pinned or is-nonstructurally-mutating bits.
My biggest concern is future Swift concurrency support. I suspect we will want storage for a thread ID or a lock. Maybe those would be uncommon enough that they can live in a side allocation. Will we want a thread ID in every thread-local object in production code so we can assert that it has not incorrectly escaped, or can we leave things like that for debug builds only?
One scheme that I want to investigate for ObjC's use is to allocate a flat array of side allocations, and store an index into that array in the isa field's extra bits. Depending on architecture that allows between 256K and 64M objects with side allocations before falling back to a global table. If that works well enough then packing everything into a single pointer-size field becomes more plausible.
Joe, did you measure the cost of a mask operation at every Swift virtual call? I can't remember.
Greg Parker gparker at apple.com Runtime Wrangler
More information about the swift-dev