Skip to content

Commit

Permalink
Add diffing optimization when result is all deletes or inserts
Browse files Browse the repository at this point in the history
Summary:
In an attempt to best other diffing libraries, I noticed that pure insert/delete diffing results would be almost 5x slower than changes between existing arrays. This is pretty much a benchmarking enhancement, but improves diffing performance by ~5x when the from-array or to-array is empty.

```
// OLD only inserts
avg: 0.007469, min: 0.006998, max: 0.016550, p50: 0.007254, p75: 0.007712, p90: 0.007899, p95: 0.008345, p99: 0.016550
// NEW
avg: 0.001392, min: 0.001256, max: 0.006772, p50: 0.001289, p75: 0.001348, p90: 0.001533, p95: 0.001614, p99: 0.006772
```

```
// OLD only deletes
avg: 0.005821, min: 0.005669, max: 0.006511, p50: 0.005766, p75: 0.005852, p90: 0.006030, p95: 0.006204, p99: 0.006511
// NEW
avg: 0.001184, min: 0.001096, max: 0.001673, p50: 0.001123, p75: 0.001212, p90: 0.001378, p95: 0.001467, p99: 0.001673
```

Note the average time improvements (seconds).

Benchmarking done on a 4s w/ this project:
https://pxl.cl/bLBB

Reviewed By: manicakes

Differential Revision: D6968683

fbshipit-source-id: 0d8e058f0aaa9ce756ca69326527d04504ac6429
  • Loading branch information
Ryan Nystrom authored and facebook-github-bot committed Feb 13, 2018
1 parent 43fe81d commit afd2d29
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ The changelog for `IGListKit`. Also see the [releases](https://github.com/instag

- Add support for UICollectionView's interactive reordering in iOS 9+. Updates include `-[IGListSectionController canMoveItemAtIndex:]` to enable the behavior, `-[IGListSectionController moveObjectFromIndex:toIndex:]` called when items within a section controller were moved through reordering, `-[IGListAdapterDataSource listAdapter:moveObject:from:to]` called when section controllers themselves were reordered (only possible when all section controllers contain exactly 1 object), and `-[IGListUpdatingDelegate moveSectionInCollectionView:fromIndex:toIndex]` to enable custom updaters to conform to the reordering behavior. The update also includes two new examples `ReorderableSectionController` and `ReorderableStackedViewController` to demonstrate how to enable interactive reordering in your client app. [Jared Verdi](https://github.com/jverdi) [(#976)](https://github.com/Instagram/IGListKit/pull/976)

- 5x improvement to diffing performance when result is only inserts or deletes. [Ryan Nystrom](https://github.com/rnystrom) [(tbd)](tbd)

### Fixes

3.2.0
Expand Down
62 changes: 59 additions & 3 deletions Source/Common/IGListDiff.mm
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,16 @@ static void addIndexToCollection(BOOL useIndexPaths, __unsafe_unretained id coll
}
};

static NSArray<NSIndexPath *> *indexPathsAndPopulateMap(__unsafe_unretained NSArray<id<IGListDiffable>> *array, NSInteger section, __unsafe_unretained NSMapTable *map) {
NSMutableArray<NSIndexPath *> *paths = [NSMutableArray new];
[array enumerateObjectsUsingBlock:^(id<IGListDiffable> obj, NSUInteger idx, BOOL *stop) {
NSIndexPath *path = [NSIndexPath indexPathForItem:idx inSection:section];
[paths addObject:path];
[map setObject:paths forKey:[obj diffIdentifier]];
}];
return paths;
}

static id IGListDiffing(BOOL returnIndexPaths,
NSInteger fromSection,
NSInteger toSection,
Expand All @@ -94,6 +104,55 @@ static id IGListDiffing(BOOL returnIndexPaths,
const NSInteger newCount = newArray.count;
const NSInteger oldCount = oldArray.count;

NSMapTable *oldMap = [NSMapTable strongToStrongObjectsMapTable];
NSMapTable *newMap = [NSMapTable strongToStrongObjectsMapTable];

// if no new objects, everything from the oldArray is deleted
// take a shortcut and just build a delete-everything result
if (newCount == 0) {
if (returnIndexPaths) {
return [[IGListIndexPathResult alloc] initWithInserts:[NSArray new]
deletes:indexPathsAndPopulateMap(oldArray, fromSection, oldMap)
updates:[NSArray new]
moves:[NSArray new]
oldIndexPathMap:oldMap
newIndexPathMap:newMap];
} else {
[oldArray enumerateObjectsUsingBlock:^(id<IGListDiffable> obj, NSUInteger idx, BOOL *stop) {
addIndexToMap(returnIndexPaths, fromSection, idx, obj, oldMap);
}];
return [[IGListIndexSetResult alloc] initWithInserts:[NSIndexSet new]
deletes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, oldCount)]
updates:[NSIndexSet new]
moves:[NSArray new]
oldIndexMap:oldMap
newIndexMap:newMap];
}
}

// if no old objects, everything from the newArray is inserted
// take a shortcut and just build an insert-everything result
if (oldCount == 0) {
if (returnIndexPaths) {
return [[IGListIndexPathResult alloc] initWithInserts:indexPathsAndPopulateMap(newArray, toSection, newMap)
deletes:[NSArray new]
updates:[NSArray new]
moves:[NSArray new]
oldIndexPathMap:oldMap
newIndexPathMap:newMap];
} else {
[newArray enumerateObjectsUsingBlock:^(id<IGListDiffable> obj, NSUInteger idx, BOOL *stop) {
addIndexToMap(returnIndexPaths, toSection, idx, obj, newMap);
}];
return [[IGListIndexSetResult alloc] initWithInserts:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, newCount)]
deletes:[NSIndexSet new]
updates:[NSIndexSet new]
moves:[NSArray new]
oldIndexMap:oldMap
newIndexMap:newMap];
}
}

// symbol table uses the old/new array diffIdentifier as the key and IGListEntry as the value
// using id<NSObject> as the key provided by https://lists.gnu.org/archive/html/discuss-gnustep/2011-07/msg00019.html
unordered_map<id<NSObject>, IGListEntry, IGListHashID, IGListEqualID> table;
Expand Down Expand Up @@ -185,9 +244,6 @@ static id IGListDiffing(BOOL returnIndexPaths,
mMoves = [NSMutableArray<IGListMoveIndex *> new];
}

NSMapTable *oldMap = [NSMapTable strongToStrongObjectsMapTable];
NSMapTable *newMap = [NSMapTable strongToStrongObjectsMapTable];

// track offsets from deleted items to calculate where items have moved
vector<NSInteger> deleteOffsets(oldCount), insertOffsets(newCount);
NSInteger runningOffset = 0;
Expand Down

0 comments on commit afd2d29

Please sign in to comment.