-
Notifications
You must be signed in to change notification settings - Fork 132
Choose the autorelease pool implementation once #410
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
DTW-Thalion
wants to merge
2
commits into
gnustep:master
Choose a base branch
from
DTW-Thalion:perf/autorelease-pool-choice
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I'd expect this to be faster. Checking a boolean and checking against nil compile to the same instruction on most architectures.
You might have some luck sticking a
__builtin_expecthere to make sure that the compiler knows that this is a slow path.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right that the two guards compile to the same thing. Master is cmpq $0, AutoreleasePool / je / ret, this is cmpb $0, chosen / jne / ret.
The difference is not the compare, it is that master's guard never closes in a program with no NSAutoreleasePool. AutoreleasePool stays Nil, so every push and every autorelease of a fast ARC object falls through and calls objc_getClass again.
Callgrind over push and pop with no Foundation: 507.5 instructions per pair on master, 70 percent of the program's instructions in objc_getClass and the class table lookup under it.
With the lookup done once, 142.6 per pair. Wall clock 27.8 ns against 5.16.
I built the __builtin_expect version. It executes the same instruction count, 1.0151 billion either way for the same run, differing by about 20 in a billion, and 27.2 ns against 27.8 where two control runs of master in the same rounds gave 27.4 and 27.5. It moves the cold block, but the call is still on the path that is always taken.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That was intentional, because it’s possible for this code path to be hit the first time before
NSAutoreleasePoolhas been loaded. It’s less likely with the v2 ABI, but happened in some +load methods with the v1 ABI.What is the use case for running without an
NSAutoreleasePoolimplementation after the program has loaded?We could move this initialisation into the class loader: when you load a new class, see if it’s NSAutoreleasePool and set this variable if so?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair objection. Choosing at first use fixes the choice for the process, so an
early +load would lock out a Foundation loaded after it. That is worse than
the current setup.
The only use case I can honestly think of is ARC code linked against libobjc2 with no Foundation
which is not a typical use case. The guard only closes once the lookup succeeds,
so there it never closes: 507.5 instructions per push and pop pair against 142.6,
27.80 ns against 5.16. With Foundation it closes on the first call and the two are identical.
The class loader version is built: same numbers, 198 of 198, and it keeps the
recovery. I didn't put this version forward for a reason:
objc_autoreleasePoolPop reads the variable without calling initAutorelease, so
a pool pushed before the switch is popped after it.
Where NSAutoreleasePool has no -_ARCCompatibleAutoreleasePool,
that pointer reaches -release of a class which did not create it, nothing drains,
and with real ivars it aborts in free(). Class registration narrows that window rather
than closing it.
So the switch is needed for later pools and wrong for any pool straddling one.
If the token from objc_autoreleasePoolPush recorded which implementation made
it, the switch would be safe whenever it came. Is there a reason it cannot?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Answering my own question: It can. The token is either an interior pointer into the ARC pool page, an
array of id, or an NSAutoreleasePool instance, which starts with an isa. Both
are pointer aligned, so the low bit is free. Push sets it on the ARC token and
objc_autoreleasePoolPop routes on the token rather than the variable.
Class registration and the tag together, on 403df0d:
198 of 198 with the tag, both on the class loader version and on master. The
tag costs 2 instructions per pair either way and nothing measurable in time.
The switch still happens with the tag: the next push goes through a Foundation
loaded late, and the earlier pool still pops through the ARC path.
Only run on Linux with clang so far.
I can replace the commit with the last row.