RFC: Diff in the background

Summary:
Experimenting with a new change. We have observed instances of very large (3k+) lists stalling, even on modern devices. This is especially noticeable if the models being diffed are:

- Immutable
- `performUpdates:` often and models are alloc/init'd each time
- The `isEqualToDiffableObject:` is sort of expensive (many `-[NSString isEqualToString:]` across thousands of models or something)

Instead of just rolling this out, I plan on experimenting with results and seeing how much of a performance and stability boost we gain w/ this. Things to measure:

- Scroll performance
- CPU stalls
- WatchDog kills on older devices
Closes https://github.com/Instagram/IGListKit/pull/841

Reviewed By: amonshiz

Differential Revision: D5364127

Pulled By: rnystrom

fbshipit-source-id: 31d50d2e4b3c7c73584d6ec521a9047efd83f315
This commit is contained in:
Ryan Nystrom 2017-07-06 09:31:00 -07:00 committed by Facebook Github Bot
parent 9f5bf3fb7e
commit 1d773aa533
2 changed files with 52 additions and 24 deletions

View file

@ -18,6 +18,8 @@ NS_SWIFT_NAME(ListExperiment)
typedef NS_OPTIONS (NSInteger, IGListExperiment) {
/// Specifies no experiments.
IGListExperimentNone = 1 << 1,
/// Test updater diffing performed on a background queue.
IGListExperimentBackgroundDiffing = 1 << 2,
};
/**

View file

@ -176,9 +176,17 @@ static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable
return;
}
IGListIndexSetResult *result = IGListDiffExperiment(fromObjects, toObjects, IGListDiffEquality, self.experiments);
// disables multiple performBatchUpdates: from happening at the same time
[self beginPerformBatchUpdatesToObjects:toObjects];
void (^updateBlock)() = ^{
const IGListExperiment experiments = self.experiments;
IGListIndexSetResult *(^performDiff)() = ^{
return IGListDiffExperiment(fromObjects, toObjects, IGListDiffEquality, experiments);
};
// block executed in the first param block of -[UICollectionView performBatchUpdates:completion:]
void (^batchUpdatesBlock)(IGListIndexSetResult *result) = ^(IGListIndexSetResult *result){
executeUpdateBlocks();
self.applyingUpdateData = [self flushCollectionView:collectionView
@ -190,7 +198,8 @@ static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable
[self performBatchUpdatesItemBlockApplied];
};
void (^completionBlock)(BOOL) = ^(BOOL finished) {
// block used as the second param of -[UICollectionView performBatchUpdates:completion:]
void (^batchUpdatesCompletionBlock)(BOOL) = ^(BOOL finished) {
executeCompletionBlocks(finished);
[delegate listAdapterUpdater:self didPerformBatchUpdates:(id)self.applyingUpdateData collectionView:collectionView];
@ -200,28 +209,45 @@ static NSArray *objectsWithDuplicateIdentifiersRemoved(NSArray<id<IGListDiffable
[self queueUpdateWithCollectionView:collectionView];
};
// disables multiple performBatchUpdates: from happening at the same time
[self beginPerformBatchUpdatesToObjects:toObjects];
@try {
[delegate listAdapterUpdater:self willPerformBatchUpdatesWithCollectionView:collectionView];
if (animated) {
[collectionView performBatchUpdates:updateBlock completion:completionBlock];
} else {
[CATransaction begin];
[CATransaction setDisableActions:YES];
[collectionView performBatchUpdates:updateBlock completion:^(BOOL finished) {
completionBlock(finished);
[CATransaction commit];
}];
// block that executes the batch update and exception handling
void (^performUpdate)(IGListIndexSetResult *) = ^(IGListIndexSetResult *result){
@try {
[delegate listAdapterUpdater:self willPerformBatchUpdatesWithCollectionView:collectionView];
if (animated) {
[collectionView performBatchUpdates:^{
batchUpdatesBlock(result);
} completion:batchUpdatesCompletionBlock];
} else {
[CATransaction begin];
[CATransaction setDisableActions:YES];
[collectionView performBatchUpdates:^{
batchUpdatesBlock(result);
} completion:^(BOOL finished) {
batchUpdatesCompletionBlock(finished);
[CATransaction commit];
}];
}
} @catch (NSException *exception) {
[delegate listAdapterUpdater:self
willCrashWithException:exception
fromObjects:fromObjects
toObjects:toObjects
updates:(id)self.applyingUpdateData];
@throw exception;
}
} @catch (NSException *exception) {
[delegate listAdapterUpdater:self
willCrashWithException:exception
fromObjects:fromObjects
toObjects:toObjects
updates:(id)self.applyingUpdateData];
@throw exception;
};
// temporary test to try out background diffing
if (IGListExperimentEnabled(experiments, IGListExperimentBackgroundDiffing)) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
IGListIndexSetResult *result = performDiff();
dispatch_async(dispatch_get_main_queue(), ^{
performUpdate(result);
});
});
} else {
IGListIndexSetResult *result = performDiff();
performUpdate(result);
}
}