Repository navigation
Conversation
reloadFromDatabase runs after every notification-extension write and calls setFeedPreview once per chat. Each call found its row with conversations.firstIndex(where:), so one reload scanned the chat list once per chat on the main actor. ConversationStore now keeps an id-to-position map, rebuilt after every insert, remove and reorder. A didSet would also fire on element writes and make each of those O(n). The store's per-id scans, the keeping* merge helpers that setFeed runs per row, and the controller's conversation(withID:) and membership checks use it. A lookup that finds the map out of date asserts in debug and falls back to a scan.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Every notification-extension write triggers
reloadFromDatabase, which callssetFeedPreviewonce per chat. Each call looked up its row withconversations.firstIndex(where:), so one reload scanned the chat list once per chat on the main actor.ConversationStorenow keeps an id-to-position map and rebuilds it after every insert, remove and reorder (setFeed,setFeed(_:type:),setGroupFeed,remove,sort). It is not adidSet, because element writes likeconversations[i].lastMessage = …fire that too and would make every write O(n). These now go through the map:firstIndex(where:)in the storekeeping*merge helpers, whichsetFeedruns once per incoming row, so a feed load was also quadraticconversation(withID:)andcontains(_:), which the controller'sconversation(withID:),displayName(forConversationID:)and membership checks now call.ActivityRowcallsconversation(withID:)once per boost row, so the activity list paid one chat-list scan per row.If the map is ever out of date, a lookup asserts in debug and falls back to a scan.
Numbers
These come from the chat-load benchmark harness (
ChatLoadBenchmarkTests) on an iPhone 17 simulator (iOS 27.0), warm, withorigin/main641184f and this branch run back to back:reloadSplit():setFeedPreviewviac.storereloadSlope():reloadFromDatabasetotalreloadSlope(): refresh per chatThe machine was heavily loaded (load average about 100), and absolute times moved between runs. An earlier
origin/mainrun measured 1444.8 ms and 4923.1 ms forreloadFromDatabaseat 1000 and 2000 chats. The comparison that holds is the back-to-back pair above. The per-chat cost is now flat, so the reload is linear in chat count.What's left is the per-chat DB reads,
latestMessageandnewestMessageID: 216.7 ms at 1000 chats and 467.6 ms at 2000, in the same run.Not done: refresh only the chats the extension touched
That needs to know which chats the extension wrote, and that isn't cheap to know today:
schemaVersionbump, and the store gets rebuilt.With the lookup fixed, that marker is the next step for this path.
Tests:
ConversationStoreLookupTestscovers lookups after an insert, aremove, anadvanceLastActivityreorder, asetFeedreplace, and asetGroupFeedthat drops a group the user left. Each test writes through the store after rows have moved and reads back by id, so an out-of-date map would put the preview on the wrong row.