-
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -132,22 +132,6 @@ | |
#define __has_attribute(x) 0 // Compatibility with non-clang compilers. | ||
#endif | ||
|
||
#ifndef NS_CONSUMED | ||
#if __has_feature(attribute_ns_consumed) | ||
#define NS_CONSUMED __attribute__((ns_consumed)) | ||
#else | ||
#define NS_CONSUMED | ||
#endif | ||
#endif | ||
|
||
#ifndef NS_RETURNS_RETAINED | ||
#if __has_feature(attribute_ns_returns_retained) | ||
#define NS_RETURNS_RETAINED __attribute__((ns_returns_retained)) | ||
#else | ||
#define NS_RETURNS_RETAINED | ||
#endif | ||
#endif | ||
|
||
#ifndef CF_RETURNS_RETAINED | ||
#if __has_feature(attribute_cf_returns_retained) | ||
#define CF_RETURNS_RETAINED __attribute__((cf_returns_retained)) | ||
|
@@ -245,26 +229,38 @@ | |
* Create a new set by mapping `collection` over `work`, ignoring nil. | ||
*/ | ||
#define ASSetByFlatMapping(collection, decl, work) ({ \ | ||
NSMutableSet *s = [NSMutableSet set]; \ | ||
CFTypeRef _cArray[collection.count]; \ | ||
NSUInteger _i = 0; \ | ||
for (decl in collection) {\ | ||
id result = work; \ | ||
if (result != nil) { \ | ||
[s addObject:result]; \ | ||
if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \ | ||
_i++; \ | ||
} \ | ||
} \ | ||
s; \ | ||
NSSet *result; \ | ||
if (_i == 0) { \ | ||
/** Zero fast path. */ \ | ||
result = [NSSet set]; \ | ||
} else if (_i == 1) { \ | ||
/** NSSingleObjectSet is fast. Create one and release. */ \ | ||
CFTypeRef val = _cArray[0]; \ | ||
result = [NSSet setWithObject:(__bridge id)val]; \ | ||
CFBridgingRelease(val); \ | ||
} else { \ | ||
CFSetCallBacks cb = kCFTypeSetCallBacks; \ | ||
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 commentThe 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. |
||
NSHashTable *t = [NSHashTable hashTableWithOptions:NSHashTableObjectPointerPersonality]; \ | ||
NSHashTable *t = [[NSHashTable alloc] initWithOptions:NSHashTableObjectPointerPersonality capacity:collection.count]; \ | ||
for (decl in collection) {\ | ||
id result = work; \ | ||
if (result != nil) { \ | ||
[t addObject:result]; \ | ||
} \ | ||
/* NSHashTable accepts nil and avoid extra retain/release. */ \ | ||
[t addObject:work]; \ | ||
} \ | ||
t; \ | ||
}) | ||
|
@@ -273,12 +269,37 @@ | |
* Create a new array by mapping `collection` over `work`, ignoring nil. | ||
*/ | ||
#define ASArrayByFlatMapping(collection, decl, work) ({ \ | ||
NSMutableArray *a = [NSMutableArray array]; \ | ||
CFTypeRef _cArray[collection.count]; \ | ||
NSUInteger _i = 0; \ | ||
for (decl in collection) {\ | ||
if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \ | ||
_i++; \ | ||
} \ | ||
} \ | ||
NSArray *result; \ | ||
if (_i == 0) { \ | ||
/** Zero array fast path. */ \ | ||
result = @[]; \ | ||
} else if (_i == 1) { \ | ||
/** NSSingleObjectArray is fast. Create one and release. */ \ | ||
CFTypeRef val = _cArray[0]; \ | ||
result = [NSArray arrayWithObject:(__bridge id)val]; \ | ||
CFBridgingRelease(val); \ | ||
} else { \ | ||
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 commentThe 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:
TODO: If someone calls |
||
result; \ | ||
}) | ||
|
||
#define ASMutableArrayByFlatMapping(collection, decl, work) ({ \ | ||
id _cArray[collection.count]; \ | ||
NSUInteger _i = 0; \ | ||
for (decl in collection) {\ | ||
id result = work; \ | ||
if (result != nil) { \ | ||
[a addObject:result]; \ | ||
if ((_cArray[_i] = work)) { \ | ||
_i++; \ | ||
} \ | ||
} \ | ||
a; \ | ||
[[NSMutableArray alloc] initWithObjects:_cArray count:_i]; \ | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,11 @@ NS_ASSUME_NONNULL_BEGIN | |
AS_SUBCLASSING_RESTRICTED | ||
@interface ASElementMap : NSObject <NSCopying, NSFastEnumeration> | ||
|
||
/** | ||
* 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 commentThe reason will be displayed to describe this comment to others. Learn more. This is required by the new implementation of |
||
|
||
/** | ||
* The number of sections (of items) in this map. | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -203,13 +203,9 @@ - (void)setRetainSublayoutLayoutElements:(BOOL)retainSublayoutLayoutElements | |
_sublayoutLayoutElements = nil; | ||
} else { | ||
// Add sublayouts layout elements to an internal array to retain it while the layout lives | ||
NSUInteger sublayoutCount = _sublayouts.count; | ||
if (sublayoutCount > 0) { | ||
_sublayoutLayoutElements = [NSMutableArray arrayWithCapacity:sublayoutCount]; | ||
for (ASLayout *sublayout in _sublayouts) { | ||
[_sublayoutLayoutElements addObject:sublayout.layoutElement]; | ||
} | ||
} | ||
_sublayoutLayoutElements = ASMutableArrayByFlatMapping(_sublayouts, ASLayout *sublayout, ({ | ||
sublayout.layoutElement; | ||
})); | ||
} | ||
} | ||
} | ||
|
@@ -236,7 +232,7 @@ - (ASLayout *)filteredNodeLayoutTree | |
queue.push_back({sublayout, sublayout.position}); | ||
} | ||
|
||
NSMutableArray *flattenedSublayouts = [NSMutableArray array]; | ||
std::vector<ASLayout *> flattenedSublayouts; | ||
|
||
while (!queue.empty()) { | ||
const Context context = queue.front(); | ||
|
@@ -255,7 +251,7 @@ - (ASLayout *)filteredNodeLayoutTree | |
position:absolutePosition | ||
sublayouts:@[]]; | ||
} | ||
[flattenedSublayouts addObject:layout]; | ||
flattenedSublayouts.push_back(layout); | ||
} else if (sublayoutsCount > 0){ | ||
std::vector<Context> sublayoutContexts; | ||
for (ASLayout *sublayout in sublayouts) { | ||
|
@@ -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 commentThe 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. |
||
// All flattened layouts must have this flag enabled | ||
// to ensure sublayout elements are retained until the layouts are applied. | ||
layout.retainSublayoutLayoutElements = 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)