Faster collection operations (#748)

* Faster collection operations

* Fix a few things

* Put the stupid semicolon

* Address warning

* Cut down retain/releases during collection operations

* Update CHANGELOG.md
This commit is contained in:
Adlai Holler 2018-01-22 05:22:03 -08:00 committed by Huy Nguyen
parent b5d3e52e8b
commit 5c13403ef7
14 changed files with 100 additions and 108 deletions

View File

@ -60,6 +60,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
disableMainThreadChecker = "YES"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"

View File

@ -14,6 +14,7 @@
- [ASCollectionNode] Added support for interactive item movement. [Adlai Holler](https://github.com/Adlai-Holler)
- Added an experimental "no-copy" rendering API. See ASGraphicsContext.h for info. [Adlai Holler](https://github.com/Adlai-Holler)
- Dropped support for iOS 8. [Adlai Holler](https://github.com/Adlai-Holler)
- Optimize internal collection operations. [Adlai Holler](https://github.com/Adlai-Holler) [#748](https://github.com/TextureGroup/Texture/pull/748)
## 2.6
- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon)

View File

@ -719,19 +719,9 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
- (NSArray<NSIndexPath *> *)convertIndexPathsToCollectionNode:(NSArray<NSIndexPath *> *)indexPaths
{
if (indexPaths == nil) {
return nil;
}
NSMutableArray<NSIndexPath *> *indexPathsArray = [NSMutableArray arrayWithCapacity:indexPaths.count];
for (NSIndexPath *indexPathInView in indexPaths) {
NSIndexPath *indexPath = [self convertIndexPathToCollectionNode:indexPathInView];
if (indexPath != nil) {
[indexPathsArray addObject:indexPath];
}
}
return indexPathsArray;
return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPathInView, ({
[self convertIndexPathToCollectionNode:indexPathInView];
}));
}
- (ASCellNode *)supplementaryNodeForElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
@ -747,17 +737,10 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
- (NSArray *)visibleNodes
{
NSArray *indexPaths = [self indexPathsForVisibleItems];
NSMutableArray *visibleNodes = [[NSMutableArray alloc] init];
for (NSIndexPath *indexPath in indexPaths) {
ASCellNode *node = [self nodeForItemAtIndexPath:indexPath];
if (node) {
// It is possible for UICollectionView to return indexPaths before the node is completed.
[visibleNodes addObject:node];
}
}
return visibleNodes;
return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPath, ({
[self nodeForItemAtIndexPath:indexPath];
}));
}
- (BOOL)usesSynchronousDataLoading
@ -2176,13 +2159,9 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
return;
}
NSMutableArray<NSIndexPath *> *uikitIndexPaths = [NSMutableArray arrayWithCapacity:nodes.count];
for (ASCellNode *node in nodes) {
NSIndexPath *uikitIndexPath = [self indexPathForNode:node];
if (uikitIndexPath != nil) {
[uikitIndexPaths addObject:uikitIndexPath];
}
}
NSArray *uikitIndexPaths = ASArrayByFlatMapping(nodes, ASCellNode *node, ({
[self indexPathForNode:node];
}));
[_layoutFacilitator collectionViewWillEditCellsAtIndexPaths:uikitIndexPaths batched:NO];

View File

@ -158,14 +158,12 @@
- (void)setupYogaCalculatedLayout
{
YGNodeRef yogaNode = self.style.yogaNode;
uint32_t childCount = YGNodeGetChildCount(yogaNode);
ASDisplayNodeAssert(childCount == self.yogaChildren.count,
ASDisplayNodeAssert(YGNodeGetChildCount(yogaNode) == self.yogaChildren.count,
@"Yoga tree should always be in sync with .yogaNodes array! %@", self.yogaChildren);
NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:childCount];
for (ASDisplayNode *subnode in self.yogaChildren) {
[sublayouts addObject:[subnode layoutForYogaNode]];
}
NSArray *sublayouts = ASArrayByFlatMapping(self.yogaChildren, ASDisplayNode *subnode, ({
[subnode layoutForYogaNode];
}));
// The layout for self should have position CGPointNull, but include the calculated size.
CGSize size = CGSizeMake(YGNodeLayoutGetWidth(yogaNode), YGNodeLayoutGetHeight(yogaNode));

View File

@ -607,15 +607,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
return nil;
}
NSMutableArray<NSIndexPath *> *indexPathsArray = [NSMutableArray new];
for (NSIndexPath *indexPathInView in indexPaths) {
NSIndexPath *indexPath = [self convertIndexPathToTableNode:indexPathInView];
if (indexPath != nil) {
[indexPathsArray addObject:indexPath];
}
}
return indexPathsArray;
return ASArrayByFlatMapping(indexPaths, NSIndexPath *indexPathInView, ({
[self convertIndexPathToTableNode:indexPathInView];
}));
}
- (NSIndexPath *)indexPathForNode:(ASCellNode *)cellNode

View File

@ -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) ({ \
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) {\
id result = work; \
if (result != nil) { \
[a addObject:result]; \
if ((_cArray[_i] = (__bridge_retained CFTypeRef)work)) { \
_i++; \
} \
} \
a; \
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); \
} \
result; \
})
#define ASMutableArrayByFlatMapping(collection, decl, work) ({ \
id _cArray[collection.count]; \
NSUInteger _i = 0; \
for (decl in collection) {\
if ((_cArray[_i] = work)) { \
_i++; \
} \
} \
[[NSMutableArray alloc] initWithObjects:_cArray count:_i]; \
})

View File

@ -59,7 +59,7 @@
+ (ASCollectionLayoutState *)calculateLayoutWithContext:(ASCollectionLayoutContext *)context
{
ASElementMap *elements = context.elements;
NSMutableArray<ASCellNode *> *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node);
NSArray<ASCellNode *> *children = ASArrayByFlatMapping(elements.itemElements, ASCollectionElement *element, element.node);
if (children.count == 0) {
return [[ASCollectionLayoutState alloc] initWithContext:context];
}

View File

@ -102,9 +102,9 @@
return [[ASCollectionLayoutState alloc] initWithContext:context];
}
NSMutableArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements,
ASCollectionElement *element,
[[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]);
NSArray<_ASGalleryLayoutItem *> *children = ASArrayByFlatMapping(elements.itemElements,
ASCollectionElement *element,
[[_ASGalleryLayoutItem alloc] initWithItemSize:itemSize collectionElement:element]);
if (children.count == 0) {
return [[ASCollectionLayoutState alloc] initWithContext:context];
}

View File

@ -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;
/**
* The number of sections (of items) in this map.
*/

View File

@ -75,6 +75,11 @@
return self;
}
- (NSUInteger)count
{
return _elementToIndexPathMap.count;
}
- (NSArray<NSIndexPath *> *)itemIndexPaths
{
return ASIndexPathsForTwoDimensionalArray(_sectionsOfItems);

View File

@ -203,13 +203,9 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT(
_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 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT(
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 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT(
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 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT(
}
}
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];
// All flattened layouts must have this flag enabled
// to ensure sublayout elements are retained until the layouts are applied.
layout.retainSublayoutLayoutElements = YES;

View File

@ -295,19 +295,15 @@ ASLayoutElementStyleExtensibilityForwarding
- (ASLayout *)calculateLayoutThatFits:(ASSizeRange)constrainedSize
{
NSArray *children = self.children;
NSMutableArray *sublayouts = [NSMutableArray arrayWithCapacity:children.count];
CGSize size = constrainedSize.min;
for (id<ASLayoutElement> child in children) {
NSArray *sublayouts = ASArrayByFlatMapping(self.children, id<ASLayoutElement> child, ({
ASLayout *sublayout = [child layoutThatFits:constrainedSize parentSize:constrainedSize.max];
sublayout.position = CGPointZero;
size.width = MAX(size.width, sublayout.size.width);
size.height = MAX(size.height, sublayout.size.height);
[sublayouts addObject:sublayout];
}
sublayout;
}));
return [ASLayout layoutWithLayoutElement:self size:size sublayouts:sublayouts];
}

View File

@ -27,13 +27,10 @@
NSMutableArray<NSMutableArray *> *ASTwoDimensionalArrayDeepMutableCopy(NSArray<NSArray *> *array)
{
NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count];
NSInteger i = 0;
for (NSArray *subarray in array) {
return ASMutableArrayByFlatMapping(array, NSArray *subarray, ({
ASDisplayNodeCAssert([subarray isKindOfClass:[NSArray class]], @"This function expects NSArray<NSArray *> *");
newArray[i++] = [subarray mutableCopy];
}
return newArray;
[subarray mutableCopy];
}));
}
void ASDeleteElementsInTwoDimensionalArrayAtIndexPaths(NSMutableArray *mutableArray, NSArray<NSIndexPath *> *indexPaths)

View File

@ -69,11 +69,9 @@ NSString * const ASTransitionContextToLayoutKey = @"org.asyncdisplaykit.ASTransi
- (NSArray<ASDisplayNode *> *)subnodesForKey:(NSString *)key
{
NSMutableArray<ASDisplayNode *> *subnodes = [NSMutableArray array];
for (ASLayout *sublayout in [self layoutForKey:key].sublayouts) {
[subnodes addObject:(ASDisplayNode *)sublayout.layoutElement];
}
return subnodes;
return ASArrayByFlatMapping([self layoutForKey:key].sublayouts, ASLayout *sublayout, ({
(ASDisplayNode *)sublayout.layoutElement;
}));
}
- (NSArray<ASDisplayNode *> *)insertedSubnodes