不可能实现的目标就像梦想,我们总是追求它们,希望它们能实现。在我最近的一次经历中,我管理了一个专题团队,C++快速项目负载(FPL),一个特殊的团队。就我个人而言,我非常热衷于性能,因为我相信它能让我们与心爱的机器进行更令人满意的交互。
随着时间的推移,大型代码库的增长,它们往往会在visualstudio中受到性能加载和构建缓慢的影响。大多数根本原因都源于我们的项目系统架构。多年来,我们取得了可观的进步(百分比),只是为了看到他们消灭了,由代码库稳定的增长率。硬件方面的改进,比如更好的cpu甚至ssd都有所帮助,但仍然没有带来很大的改变。
这个问题需要一个“不可能的目标”,所以我们决定目标很高,将解决方案加载时间提高了10倍!疯了,不是吗?尤其是因为多年来,我们几乎没有什么小的改进。 目标设定?好了,走,走,走!
几年前,在使用visualstudio图形调试器时,我遇到了一个类似的问题,即加载巨大的捕获文件,这需要渲染(有时在REF驱动程序下,非常慢),这些都需要很长时间,特别是对于复杂的图形应用程序。当时,我采用了一种缓存机制,它允许我们扩展和重用以前的计算,大大减少了重新加载时间和内存消耗。
对于FPL,大约半年前,我们开始采用类似的策略。幸运的是,我们有一个很好的开始,从一个原型,我们创造了3年前,我们没有时间完成它在那个时候。
这一次,所有的恒星最终都排列成一条直线,我们能够投入宝贵的资源来实现这一点。这是一次非同寻常的旅程,因为我们必须以非常快的速度交付,这一特性可能会破坏许多功能,其优点仅仅是性能提升。
我们开始使用非常大的解决方案,建立了一个良好的基线。我们有机会获得伟大的现实世界的解决方案(并不总是很容易找到,考虑到IP限制)以及我们的内部和生成的解决方案。我们喜欢强调超出原始设计尺寸(500个项目)的尺寸。这一次,为了获得一次好的体验,我们把目标推到了“不可能的目标”(10倍)。
其主要目标是改进解决方案加载时间并大幅减少内存消耗。在最初的设计中,我们总是像第一次看到它们一样加载项目,评估它们的值,并将它们保存在内存中,随时可以编辑。从遥测数据来看,后者完全没有必要,因为大多数用户场景都是“只读的”。 这是第一个大需求,即设计一个“只读”的项目系统,能够为visualstudio组件提供所需的信息,visualstudio组件不断地查询这些信息(设计时工具、IntelliSense、扩展)。第二个要求是确保我们尽可能地重用以前的负载。
我们将所有项目的“真实”加载和“评估”转移到一个out-of-proc服务中,该服务使用SQLite存储数据并按需提供服务。这也给了我们一个很好的机会来并行化项目加载,这本身就提供了很大的性能改进。迁移到outofproc还增加了一个很大的好处,即减少了visualstudio进程中的内存占用,对于中等大小的解决方案,我说的是数百MB,对于大型解决方案,甚至是GB范围(2-3k项目解决方案)。这并不意味着我们只是将内存使用转移到其他地方,实际上我们依赖于SQLite存储,而且我们不必再加载MSBuild后面的重对象模型。
我们取得了渐进式的进展,并利用客户对预发布版的反馈来调整和改进我们的解决方案。我们启用的第一个项目类型是Desktop,因为它是主导类型,其次是CLI项目类型。所有不受支持的项目类型都将像早期版本一样“完全”加载,因此它们将正常工作,但没有FPL的好处。
令人着迷的是,在最初的设计没有考虑到可能的大量负载的地方,您可以发现意外引入的N^2算法。它们很小,相对于最初的大时间来说,但是一旦我们添加了缓存层,它们就会被放大。我们修复了其中几个,这进一步提高了性能。我们还花了大量时间试图减小内存中大量count对象的大小,主要是解决方案项的内部表示。
从可用性的角度来看,我们继续允许用户编辑他们的项目,当然,一旦他们尝试“编辑”,我们就无缝地加载真正的基于MSBuild的项目,并将其委托给它, 允许用户进行更改并保存。
我们还没有完成,因为我们还有很多工作要做。从客户反馈中,我们了解到,即使磁盘上的时间戳发生变化(只要内容相同,常见的情况是:git分支切换、CMake重新生成),我们也需要强化我们的功能,以维护缓存。
不可能的目标 就像这些神奇的指导方针,给你长期的方向,让你打破模式,让我们公平地说,束缚我们的思想到预先存在的解决方案。梦想远大,追求它!这证明是一个伟大的战略,因为它使我们能够探索现成的途径,并最终产生了惊人的结果。不要期待瞬间的满足,实现伟大的事情需要很长的时间,但是总是要有很高的目标,因为当你回首过去,看到自己离一个曾经不可能实现的梦想有多近的时候,这是值得的。