Optimize data structures and defer layout work#160
Conversation
| if MagazineLayout._enableExperimentalOptimizations { | ||
| newHeaderLocationsForFlattenedIndices.removeAll(keepingCapacity: true) | ||
| newFooterLocationsForFlattenedIndices.removeAll(keepingCapacity: true) | ||
| newBackgroundLocationsForFlattenedIndices.removeAll(keepingCapacity: true) | ||
| newItemLocationsForFlattenedIndices.removeAll(keepingCapacity: true) | ||
|
|
||
| for sectionIndex in 0..<sectionModels.count { | ||
| if sectionModels[sectionIndex].headerModel != nil { | ||
| let elementLocation = ElementLocation(elementIndex: 0, sectionIndex: sectionIndex) | ||
| newHeaderLocationsForFlattenedIndices.append(elementLocation) | ||
| } | ||
|
|
||
| if sectionModels[sectionIndex].footerModel != nil { | ||
| let elementLocation = ElementLocation(elementIndex: 0, sectionIndex: sectionIndex) | ||
| newFooterLocationsForFlattenedIndices.append(elementLocation) | ||
| } | ||
|
|
||
| if sectionModels[sectionIndex].backgroundModel != nil { | ||
| backgroundLocationsForFlattenedIndices[flattenedBackgroundIndex] = ElementLocation( | ||
| elementIndex: 0, | ||
| sectionIndex: sectionIndex) | ||
| flattenedBackgroundIndex += 1 | ||
| if sectionModels[sectionIndex].backgroundModel != nil { | ||
| let elementLocation = ElementLocation(elementIndex: 0, sectionIndex: sectionIndex) | ||
| newBackgroundLocationsForFlattenedIndices.append(elementLocation) | ||
| } | ||
|
|
||
| for itemIndex in 0..<sectionModels[sectionIndex].numberOfItems { | ||
| let elementLocation = ElementLocation(elementIndex: itemIndex, sectionIndex: sectionIndex) | ||
| newItemLocationsForFlattenedIndices.append(elementLocation) | ||
| } |
There was a problem hiding this comment.
same logic as the existing else branch, but using the new arrays instead of dictionaries
| headerLocationsForFlattenedIndices.removeAll() | ||
| footerLocationsForFlattenedIndices.removeAll() | ||
| backgroundLocationsForFlattenedIndices.removeAll() | ||
| itemLocationsForFlattenedIndices.removeAll() | ||
|
|
||
| var flattenedHeaderIndex = 0 | ||
| var flattenedFooterIndex = 0 | ||
| var flattenedBackgroundIndex = 0 | ||
| var flattenedItemIndex = 0 | ||
| for sectionIndex in 0..<sectionModels.count { | ||
| if sectionModels[sectionIndex].headerModel != nil { | ||
| headerLocationsForFlattenedIndices[flattenedHeaderIndex] = ElementLocation( | ||
| elementIndex: 0, | ||
| sectionIndex: sectionIndex) | ||
| flattenedHeaderIndex += 1 | ||
| } | ||
|
|
||
| if sectionModels[sectionIndex].footerModel != nil { | ||
| footerLocationsForFlattenedIndices[flattenedFooterIndex] = ElementLocation( | ||
| elementIndex: 0, | ||
| sectionIndex: sectionIndex) | ||
| flattenedFooterIndex += 1 | ||
| } | ||
|
|
||
| if sectionModels[sectionIndex].backgroundModel != nil { | ||
| backgroundLocationsForFlattenedIndices[flattenedBackgroundIndex] = ElementLocation( | ||
| elementIndex: 0, | ||
| sectionIndex: sectionIndex) | ||
| flattenedBackgroundIndex += 1 | ||
| } | ||
|
|
||
| for itemIndex in 0..<sectionModels[sectionIndex].numberOfItems { | ||
| itemLocationsForFlattenedIndices[flattenedItemIndex] = ElementLocation( | ||
| elementIndex: itemIndex, | ||
| sectionIndex: sectionIndex) | ||
| flattenedItemIndex += 1 | ||
| for itemIndex in 0..<sectionModels[sectionIndex].numberOfItems { | ||
| itemLocationsForFlattenedIndices[flattenedItemIndex] = ElementLocation( | ||
| elementIndex: itemIndex, | ||
| sectionIndex: sectionIndex) | ||
| flattenedItemIndex += 1 | ||
| } |
There was a problem hiding this comment.
existing code, just indented and put in an else branch
| private var newItemIndicesForRowIndices = [ClosedRange<Int>?]() | ||
| private var newRowIndicesForItemIndices = [Int?]() | ||
| private var newItemRowHeightsForRowIndices = [CGFloat]() |
There was a problem hiding this comment.
more dictionary -> array flattening. I also simplified some types, like using a ClosedRange<Int> to represent the item indices in a particular row of content, rather than using an array for that. The indices in a row are always contiguous, so no reason to incur array allocation overhead just to say "row 5 contains item indices 9...10"
| if MagazineLayout._enableExperimentalOptimizations { | ||
| newItemIndicesForRowIndices.grow(toInclude: rowIndex, fillingWith: nil) | ||
| if let range = newItemIndicesForRowIndices[rowIndex] { | ||
| newItemIndicesForRowIndices[rowIndex] = range.lowerBound...itemIndex | ||
| } else { | ||
| newItemIndicesForRowIndices[rowIndex] = itemIndex...itemIndex | ||
| } | ||
| newRowIndicesForItemIndices.grow(toInclude: itemIndex, fillingWith: nil) | ||
| newRowIndicesForItemIndices[itemIndex] = rowIndex | ||
| } else { | ||
| itemIndicesForRowIndices[rowIndex] = itemIndicesForRowIndices[rowIndex] ?? [] | ||
| itemIndicesForRowIndices[rowIndex]?.append(itemIndex) | ||
| rowIndicesForItemIndices[itemIndex] = rowIndex | ||
| } |
There was a problem hiding this comment.
like all of these if/else cases, the false branch is existing unchanged code, and the true branch is the equivalent logic but operating on the new arrays rather than the old dictionaries.
256e4ca to
4c2ab4e
Compare
4c2ab4e to
a52e6e1
Compare
| @@ -936,7 +936,7 @@ public final class MagazineLayout: UICollectionViewLayout { | |||
| } | |||
|
|
|||
| if context.invalidateLayoutMetrics && shouldInvalidateLayoutMetrics { | |||
| prepareActions.formUnion([.updateLayoutMetrics]) | |||
| prepareActions.formUnion(.updateLayoutMetrics) | |||
There was a problem hiding this comment.
prevent unnecessary array allocations
Details
Another round of perf fixes - these are the trickiest ones. There are two main improvements:
All changes are gated behind the new experiment optimizations flag.
How Has This Been Tested
Tested in demo app and Airbnb app. Additionally, all unit tests pass.
Types of changes