-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Faster collection operations #748
Conversation
@@ -265,7 +261,8 @@ - (ASLayout *)filteredNodeLayoutTree | |||
} | |||
} | |||
|
|||
ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:flattenedSublayouts]; | |||
NSArray *sublayoutsArray = [NSArray arrayWithObjects:flattenedSublayouts.data() count:flattenedSublayouts.size()]; | |||
ASLayout *layout = [ASLayout layoutWithLayoutElement:_layoutElement size:_size sublayouts:sublayoutsArray]; |
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.
IIRC this method is one of our hottest methods, and we copy the resulting array so I'm hopeful this will be a nice bump.
@@ -60,6 +60,7 @@ | |||
buildConfiguration = "Debug" | |||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" | |||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" | |||
disableMainThreadChecker = "YES" |
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.
This disables the main thread checker for unit tests and there are cases where we expect to call UIKit methods off-main during unit tests.
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.
Interesting, I'm a bit curious which things we perform off-main intentionally in tests? I wonder if there is a way to do that in-situ, since the general existence of this checker does have value (e.g. if you were to accidentally call UIKit on main in a regression sense, then a unit test could catch it)
/** | ||
* The total number of elements in this map. | ||
*/ | ||
@property (readonly) NSUInteger count; |
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.
This is required by the new implementation of ASArrayByFlatMapping
, in addition to NSFastEnumeration conformance.
CFArrayCallBacks cb = kCFTypeArrayCallBacks; \ | ||
cb.retain = NULL; \ | ||
result = (__bridge NSArray *)CFArrayCreate(kCFAllocatorDefault, _cArray, _i, &cb); \ | ||
} \ |
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'm going to factor this out a bit and clean it up, but here's the approach:
- We need to safely retain the result of the work (if the work's result isn't retained e.g.
[NSObject new]
. Only ARC knows!) - We want to transfer that retain into the array, instead of having it retain and then we release.
- The way is, use
__bridge_retained
to tell ARC "make sure this is a +1, and I'll take responsibility for it." - Then when you create the array, use
CFArray
and provideNULL
for the retain callback. Boom, CFArray won't retain, but it will release when the array goes away. - Fast paths for 0- or 1- element collections e.g.
__NSArray0
and__NSSingleObjectArray
. - Not possible with
id []
because ARC will always release the elements when the symbol goes out of scope – there is noCF_CONSUMED
for c-arrays. - Down from (2N retains, N) releases to (N retains, 0 releases). Plus we get an immutable result, so free copying.
TODO: If someone calls CFArrayCreateCopy
, there's no short-circuit and the callbacks will be copied as well, so the objects will get overreleased. Same if they use -mutableCopy
. The solution is to create a retain callback that checks for the presence of a pthread_key
that we will set during array creation. If the key is there, skip the retain. Otherwise, retain normally.
Note that, as much as I love this diff, copy-on-write support was added to CoreFoundation for arrays, sets, and dictionaries. So copying a mutable array gives you an |
This is extremely cool. It could be enough to avoid 1 frame drop in several common scenarios, either faster devices that are near the threshold or dialing back the severity of an inevitable drop on a slower device. There is an awful lot of runtime traffic in common codepaths that run on main — just thousands of very short method calls, which do contend with background threads, especially when any part of the system is using dynamically-implemented methods. This should reduce the surface area for contention (and thus risk for the longer kinds of sporadic stalls / priority inversion and rapid priority donation thrash), and also reduce total CPU time even when un-contended. I'd like to study the code and profile this in more detail, but if you develop confidence in it, then don't wait on me to land. |
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 is pretty cool!
cb.retain = NULL; \ | ||
result = (__bridge NSSet *)CFSetCreate(kCFAllocatorDefault, _cArray, _i, &cb); \ | ||
} \ | ||
result; \ | ||
}) | ||
|
||
/** | ||
* Create a new ObjectPointerPersonality NSHashTable by mapping `collection` over `work`, ignoring nil. | ||
*/ | ||
#define ASPointerTableByFlatMapping(collection, decl, work) ({ \ |
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.
Is this one not yet used? Just wanted to say, I think this #define-based approach is very powerful as an optimization, in its ability to avoid wrapping and retains that most other mechanisms lead to. We should use it judiciously, but I bet there are a few more cases where it is justified and impactful.
4bdba96
to
5ea8c67
Compare
Generated by 🚫 Danger |
This reverts commit 5c13403.
* Faster collection operations * Fix a few things * Put the stupid semicolon * Address warning * Cut down retain/releases during collection operations * Update CHANGELOG.md
The idea here is, standardize on
ASArrayByFlatMapping
and make that macro faster and return an immutable array.Immutable arrays mean (1) free copying, and (2) free zero-arrays and optimized singleObjectArrays, which means less retaining/releasing. Yay!
I haven't profiled this yet. Just want to put it out there.