Improve room list sort performance by caching common variables

This won't help much if the user is in a ton of highly active rooms, but for the most part this will help those in thousands of rooms, many of which are likely to be quiet. 

Fixes https://github.com/vector-im/riot-web/issues/7646
Fixes https://github.com/vector-im/riot-web/issues/7645 (due to timestamp ordering)
pull/21833/head
Travis Ralston 2018-11-01 16:28:13 -06:00
parent 272acfa2f5
commit 0c7aadb92b
1 changed files with 120 additions and 15 deletions

View File

@ -54,6 +54,7 @@ class RoomListStore extends Store {
"im.vector.fake.archived": [],
},
ready: false,
roomCache: {}, // roomId => { cacheType => value }
};
}
@ -85,6 +86,8 @@ class RoomListStore extends Store {
!payload.isLiveUnfilteredRoomTimelineEvent ||
!this._eventTriggersRecentReorder(payload.event)
) break;
this._clearCachedRoomState(payload.event.getRoomId());
this._generateRoomLists();
}
break;
@ -112,6 +115,8 @@ class RoomListStore extends Store {
if (liveTimeline !== eventTimeline ||
!this._eventTriggersRecentReorder(payload.event)
) break;
this._clearCachedRoomState(payload.event.getRoomId());
this._generateRoomLists();
}
break;
@ -222,12 +227,20 @@ class RoomListStore extends Store {
// thousand times.
const pinUnread = SettingsStore.getValue("pinUnreadRooms");
const pinMentioned = SettingsStore.getValue("pinMentionedRooms");
this._timings = {};
Object.keys(lists).forEach((listKey) => {
let comparator;
switch (RoomListStore._listOrders[listKey]) {
case "recent":
comparator = (roomA, roomB) => {
return this._recentsComparator(roomA, roomB, pinUnread, pinMentioned);
this._timings["overall_" + roomA.roomId + "_" + roomB.roomId] = {
type: "overall",
start: performance.now(),
end: 0,
};
const ret = this._recentsComparator(roomA, roomB, pinUnread, pinMentioned);
this._timings["overall_" + roomA.roomId + "_" + roomB.roomId].end = performance.now();
return ret;
};
break;
case "manual":
@ -238,12 +251,76 @@ class RoomListStore extends Store {
lists[listKey].sort(comparator);
});
// Combine the samples for performance metrics
const samplesByType = {};
for (const sampleName of Object.keys(this._timings)) {
const sample = this._timings[sampleName];
if (!samplesByType[sample.type]) samplesByType[sample.type] = {
min: 999999999,
max: 0,
count: 0,
total: 0,
};
const record = samplesByType[sample.type];
const duration = sample.end - sample.start;
if (duration < record.min) record.min = duration;
if (duration > record.max) record.max = duration;
record.count++;
record.total += duration;
}
for (const category of Object.keys(samplesByType)) {
const {min, max, count, total} = samplesByType[category];
const average = total / count;
console.log(`RoomListSortPerf : type=${category} min=${min} max=${max} total=${total} samples=${count} average=${average}`);
}
this._setState({
lists,
ready: true, // Ready to receive updates via Room.tags events
});
}
_updateCachedRoomState(roomId, type, value) {
const roomCache = this._state.roomCache;
if (!roomCache[roomId]) roomCache[roomId] = {};
if (value) roomCache[roomId][type] = value;
else delete roomCache[roomId][type];
this._setState({roomCache});
}
_clearCachedRoomState(roomId) {
const roomCache = this._state.roomCache;
delete roomCache[roomId];
this._setState({roomCache});
}
_getRoomState(room, type) {
const roomId = room.roomId;
const roomCache = this._state.roomCache;
if (roomCache[roomId] && typeof roomCache[roomId][type] !== 'undefined') {
return roomCache[roomId][type];
}
if (type === "timestamp") {
const ts = this._tsOfNewestEvent(room);
this._updateCachedRoomState(roomId, "timestamp", ts);
return ts;
} else if (type === "unread") {
const unread = room.getUnreadNotificationCount() > 0;
this._updateCachedRoomState(roomId, "unread", unread);
return unread;
} else if (type === "notifications") {
const notifs = room.getUnreadNotificationCount("highlight") > 0;
this._updateCachedRoomState(roomId, "notifications", notifs);
return notifs;
} else throw new Error("Unrecognized room cache type: " + type);
}
_eventTriggersRecentReorder(ev) {
return ev.getTs() && (
Unread.eventTriggersUnreadCount(ev) ||
@ -270,30 +347,58 @@ class RoomListStore extends Store {
}
_recentsComparator(roomA, roomB, pinUnread, pinMentioned) {
//console.log("Comparing " + roomA.roomId + " with " + roomB.roomId +" || pinUnread=" + pinUnread +" pinMentioned="+pinMentioned);
// We try and set the ordering to be Mentioned > Unread > Recent
// assuming the user has the right settings, of course
// assuming the user has the right settings, of course.
this._timings["timestamp_" + roomA.roomId + "_" + roomB.roomId] = {
type: "timestamp",
start: performance.now(),
end: 0,
};
const timestampA = this._getRoomState(roomA, "timestamp");
const timestampB = this._getRoomState(roomB, "timestamp");
const timestampDiff = timestampB - timestampA;
this._timings["timestamp_" + roomA.roomId + "_" + roomB.roomId].end = performance.now();
if (pinMentioned) {
const mentionsA = roomA.getUnreadNotificationCount("highlight") > 0;
const mentionsB = roomB.getUnreadNotificationCount("highlight") > 0;
if (mentionsA && !mentionsB) return -1;
if (!mentionsA && mentionsB) return 1;
if (mentionsA && mentionsB) return 0;
// If neither have mentions, fall through to remaining checks
this._timings["mentioned_" + roomA.roomId + "_" + roomB.roomId] = {
type: "mentioned",
start: performance.now(),
end: 0,
};
const mentionsA = this._getRoomState(roomA, "notifications");
const mentionsB = this._getRoomState(roomB, "notifications");
this._timings["mentioned_" + roomA.roomId + "_" + roomB.roomId].end = performance.now();
if (mentionsA && !mentionsB) return -1;
if (!mentionsA && mentionsB) return 1;
// If they both have notifications, sort by timestamp.
// If neither have notifications (the fourth check not shown
// here), then try and sort by unread messages and finally by
// timestamp.
if (mentionsA && mentionsB) return timestampDiff;
}
if (pinUnread) {
const unreadA = Unread.doesRoomHaveUnreadMessages(roomA);
const unreadB = Unread.doesRoomHaveUnreadMessages(roomB);
this._timings["unread_" + roomA.roomId + "_" + roomB.roomId] = {
type: "unread",
start: performance.now(),
end: 0,
};
const unreadA = this._getRoomState(roomA, "unread");
const unreadB = this._getRoomState(roomB, "notifications");
this._timings["unread_" + roomA.roomId + "_" + roomB.roomId].end = performance.now();
if (unreadA && !unreadB) return -1;
if (!unreadA && unreadB) return 1;
if (unreadA && unreadB) return 0;
// If neither have unread messages, fall through to remaining checks
// If they both have unread messages, sort by timestamp
// If nether have unread message (the fourth check not shown
// here), then just sort by timestamp anyways.
if (unreadA && unreadB) return timestampDiff;
}
// XXX: We could use a cache here and update it when we see new
// events that trigger a reorder
return this._tsOfNewestEvent(roomB) - this._tsOfNewestEvent(roomA);
return timestampDiff;
}
_lexicographicalComparator(roomA, roomB) {