我们在 Heckel 算法的基础上进行改良优化,追踪记录移动操作,区分出直接移动与间接移动,并将间接移动部分进行过滤删除,最终得到满足 QQ 9 各项指标要求的 Diff 算法。如下图示例,ID5 直接移动到第一行,ID1-4 都是间接往下移动。
记录直接移动的偏移量(move = insert X delete Y的偏移量都需要记录),修正间接/被动移动的结果(ID 1-4的移动)
并行预布局
异步布局作为业界的最佳实践,自然不能在 QQ 9 上缺席。我们也进一步尝试将异步布局并行化,深挖性能极限。
首先尝试了 N 条消息 N 个线程的方案:用 GCD 派发 N 个并发任务,然后用 DispatchGroup 等待这些任务执行完成。通过并行预布局,将原本一个线程需要几十毫秒的预布局减少到了十几毫秒。这个方案后来发现了 2 个问题:
- 并行布局 N 条消息的总耗时还是比串行布局一条消息的耗时要大得多,受限于 CPU 核心数,代码中的锁或其他资源竞争导致 N 条消息的参数准备和布局计算没有能充分的并行。
- 这N条消息的布局任务分别和 N 个 GCD 任务一对一绑定了,GCD 调度这 N 个任务中有任何一个调度慢都会拉长整个预布局的耗时。
充分利用多核CPU的算力;使用并行计算,布局计算的总耗时减少了约76%。
调整后的方案如上图所示,使用了 M 个执行者来执行N条消息的布局任务(N>=M>0)。当前线程(异步布局主线程)来执行 1 个执行者,然后再由 GCD 额外调度(M-1)个线程来执行(M-1)个执行者。 首先将待计算的消息放入一个队列中,每个执行者都会循环从待计算的消息队列中取出一条消息执行布局计算,直到待计算的消息队列为空。因为消息的布局任务没有和任何一个执行者绑定,即使有执行者较长时间没有被调度也不会导致布局计算迟迟无法完成,大部分情况下这 M 个执行者会被 M 个线程并行执行。
并行布局的总耗时会随着并发线程的增加而减少,当增加到5以后耗时就基本没有怎么减少了。
看上去目前布局计算的工作已经从主线程挪走了,现实是很多时候计算出来的坐标与大小并没有与屏幕的像素点大小吻合,此时系统会在主线程再做一次“像素对齐”。在“异步布局”时也不能忽略该细节,才能确确实实减少主线程的负担,如下图所示。