Free a hidden class and stop it copying a dispatch table - #416
Draft
DTW-Thalion wants to merge 3 commits into
Draft
Free a hidden class and stop it copying a dispatch table#416DTW-Thalion wants to merge 3 commits into
DTW-Thalion wants to merge 3 commits into
Conversation
A hidden class is made with uninstalled_dtable and keeps it until the object is next messaged. A method is recorded in the class as it is installed into a dtable, in installMethodInDtable, and objc_update_dtable_for_class returns immediately for a class that has no dtable, so the class_addMethod that puts .cxx_destruct on a new hidden class does not reach the field. class->cxx_destruct therefore stays null, call_cxx_destruct finds nothing to call, and deallocHiddenClass never runs. The hidden class, its reference list and every association the object holds are never freed. An object that is messaged again after gaining an association gets a dtable, and with it the cleanup, which is why this is not seen more often. The field is recorded when the hidden class is made. Test/AssociatedObjectCleanup.m gives an object a retained association, hands the last reference to it, and destroys the object without messaging it again. The associated object's dealloc does not run before this change and does run after it.
A hidden class holds the association list and adds no methods of its own, but it built a copy of its superclass's dispatch table the first time the object was messaged after gaining an association, and never changed it. The copy is most of what an association costs. It now answers sends from its superclass's table, and gets one of its own only when a method is added to it through object_addMethod_np or object_replaceMethod_np. deallocHiddenClass does not free a table the class does not own. The .cxx_destruct is recorded in the class rather than added as a method, which the previous commit already does. Adding it as a method here would install deallocHiddenClass in the table the superclass is using, and every instance of that class would run it. Resident bytes per object holding one association, over 100000 objects: 6723 to 516 where the object is messaged again after associating, and 579 to 516 where it is not. The two cases now cost the same. Suite passes, 202 of 202.
The reference list is allocated with the hidden class, zeroed there and
walked in full when the object is destroyed, so its length is paid by
every object that carries an association. Ten slots was a guess, and the
Microsoft fork of this runtime still carries the same ten.
WinObjC's key value observing, which is the heavy user of associations,
puts one association on an observed instance: NSKVOSupport.mm has a single
instance key holding the observation info, and the selector map in
NSKVOSwizzling.mm is associated with the class rather than the instance.
Four slots costs 293 bytes per object holding one association against 440,
and an object with more than four gains a second block rather than a
larger first one.
ns per objc_getAssociatedObject, reading the key added last, and resident
bytes per object over 50000 objects:
size 2 size 4 size 6 size 10
bytes 246 293 346 440
get of 4 2.90 2.90 2.68 2.68
get of 8 3.34 3.12 3.12 3.12
get of 16 5.29 4.29 4.05 3.88
Reading is unchanged up to eight associations. Suite passes, 202 of 202 at
every size measured.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A hidden class is made with uninstalled_dtable and keeps it until the object is next messaged. A method is recorded in the class only as it is installed into a dtable, so the class_addMethod that puts .cxx_destruct on a new hidden class never reaches the field. deallocHiddenClass never runs, and the class, its reference list and every association the object holds are never freed.
The field is recorded when the class is made, and .cxx_destruct is no longer added as a method, which keeps it out of a shared table.
The class holds no methods of its own, so it answers sends from its superclass's table rather than copying one it would never change. It gets its own when a method is added through object_addMethod_np or object_replaceMethod_np, and does not free a table it does not own.
The reference list drops from ten slots to four. WinObjC's key value observing puts one association on an observed instance.
Resident bytes per object with one association: 6723 to 322 messaged again, 579 to 322 where it is not.
Test/AssociatedObjectCleanup.m covers the first change. Suite passes, 202 of 202.