优化 GNOME 应用程序

我们优化什么?

当我们为 GNOME 优化时,首先要记住的是:我们不是试图让程序更好,而是试图让使用计算机的人更快乐。

更好的程序能让人更快乐,但有些改进会让他们比其他改进更快乐:响应速度、启动时间、易于访问的命令,以及避免计算机在打开两个以上的程序时进入交换空间。

传统的优化会涉及 CPU 使用率、代码大小、鼠标点击次数和程序的内存使用量等概念。第二个列表是为了与第一个列表相关联而选择的,但有一个重要的区别:使用 GNOME 的人不在乎第二个列表,但他们非常关心第一个列表。在优化 GNOME 程序时,我们会减少 CPU 使用率、内存使用量以及所有这些,但这些只是手段,而不是最终目标。我们正在为人们优化。

优化

上一节省略了一个重要的限定:要优化某件事,它必须是可测量的。你无法衡量幸福感。但是,你可以衡量启动时间,因此可以判断你是否改进了它。希望幸福感会随之而来。

优化是测量、改进和重新测量过程。因此,你首先要做的是找到一种衡量你正在优化的内容的方法。理想情况下,这种测量应该是一个单一的数字,例如:执行一项任务所花费的时间。这是你的基准,这是告诉你你是在赢还是输的唯一方法。一个应该很快的程序和一个确实很快的程序之间存在很大的差异。

一旦你有了基本的基准,你需要找出你的代码为什么没有达到应有的效果。通过检查来做到这一点很诱人:只是查看代码并尝试发现需要改进的东西。你肯定会错了。使用分析器获得程序实际执行情况的详细分解是确保唯一的方法。

通常,问题隔离在代码的小部分。先选择最糟糕的地方并集中精力解决它。完成后,重新运行分析器并重复。随着你继续进行,每一步获得的好处会越来越少,在某个时候你必须决定结果是否足够好。如果你的努力只能提取 10% 的改进,那么你已经超过了应该停止的点。

不要忘记大局。例如,不要只是试图加速一段代码,问问自己是否需要运行它。是否可以将其与其他代码结合起来?是否可以保存并重用以前计算的结果?如果它位于用户永远不会注意到的地方,则甚至不需要对其进行优化。更糟糕的是,代码可能已经过优化,现在正在执行繁重的计算以避免稍后执行它们。代码不是孤立运行的,优化过程也是如此。

提示

基础知识

1. 在你对代码进行每次更改后重新运行基准测试,并记录你所做的所有更改以及它如何影响基准测试。这让你能够撤消错误,并帮助你避免重复错误。

2. 确保你的代码正确且无错误,然后再对其进行优化。检查在优化后它是否仍然正确且无错误。

  1. 先在高级别进行优化,然后再优化细节。

4. 使用正确的算法。经典的教科书示例是使用快速排序而不是冒泡排序。还有很多其他的,有些可以节省内存,有些可以节省 CPU。此外,看看你可以采取哪些捷径:如果你准备做出一些妥协,你可以比快速排序更快。

5. 优化是一种权衡。缓存结果可以加快计算速度,但会增加内存使用量。将数据保存到磁盘可以节省内存,但在从磁盘加载回来时会花费时间。

6. 确保你选择各种各样的输入来进行优化。如果你不这样做,很容易最终得到一段针对一个文件而精心优化的代码,而对其他文件则无效。

7. 避免代价高昂的操作:多次小的磁盘读取。使用大量的内存,导致磁盘交换成为必要。避免任何不必要地从硬盘驱动器读取或写入的内容。网络也很慢。此外,避免需要从 X 服务器获得响应的图形操作。

对粗心大意的人的陷阱

1. 警惕副作用。不同代码部分之间经常存在奇怪的相互作用,一个部分的加速可能会减慢另一个部分的速度。

2. 在对代码进行计时时,即使在安静的系统上,外部事件也会给计时结果增加噪声。在多次运行中取平均值。如果代码非常短,计时器分辨率也是一个问题。在这种情况下,测量计算机运行代码 100 或 1000 次所花费的时间。如果记录的时间超过几秒,你应该没问题。

3. 很容易被分析器误导。有报道称人们优化了操作系统空闲循环,因为在那里花费了所有时间!不要优化用户不在乎的代码。

4. 记住 X 服务器上的资源。你的程序的内存使用量不包括存储在 X 服务器进程中的像素图,但它们仍然在使用内存。使用 xrestop 查看你的程序正在使用的资源。

低级提示

1. 在优化内存使用时,要注意峰值使用量和平均内存使用量之间的区别。有些内存几乎总是分配的,这通常不好。有些内存只是短暂分配的,这可能完全可以接受。像 massif 这样的工具使用空间时间的概念,即内存使用量和分配持续时间的乘积。

2. 对只做你知道是必需的事情的简化代码进行计时,这可以给出你的代码所花费时间的绝对下限。例如,在优化循环时,计时空循环。如果它仍然太长,那么任何微优化都无法帮助,你必须更改你的设计。确保编译器没有优化掉你的空循环。

3. 将代码从循环中移出。执行一次的稍微复杂一些的代码比执行一千次的简单代码快得多。避免频繁调用慢速代码。

4. 尽可能多地向编译器提供提示。使用 const 关键字。对短而频繁调用的函数使用 G_INLINE_FUNC。查找 G_GNUC_PUREG_LIKELY 和其他 GLib 杂项宏。为了确保可移植性,请使用宏而不是特定于编译器的关键字。

5. 不要使用汇编语言。它不可移植,虽然它可能在一个处理器上很快,但甚至不能保证在支持该体系结构的所有处理器上都很快。

6. 除非你确定现有的库例程不必要地慢,否则不要重写它。许多 CPU 密集型库例程已经过优化。相反,有些库例程很慢,尤其是那些对操作系统进行系统调用的库例程。

7. 尽量减少你链接的库的数量。链接的库越少,程序启动速度越快。这对于 GNOME 来说可能是一件困难的事情。

高级技巧

1. 利用并发。这不仅仅意味着使用多个处理器,还意味着利用用户花时间思考他们接下来要做什么的时间来预先执行一些计算。在等待数据从磁盘加载时执行计算。利用多种资源,一次性使用它们全部。

2. 作弊。用户只需要认为计算机很快,它实际上是否很快并不重要。重要的是命令和答案之间的时间,即使响应是预先计算的、缓存的,或者实际上将在稍后更方便的时候计算出来,只要用户得到他们期望的结果即可。

3. 在空闲循环中执行操作。这比使用完整的多线程更容易编程,但仍然可以在用户不知情的情况下完成任务。但要小心,如果你在空闲循环中花费的时间太长,你的程序会变得迟缓。因此,定期将控制权交还给主循环。

4. 如果万事皆休,告诉用户代码会很慢,并显示一个进度条。他们不会像你直接呈现结果那样快乐,但至少他们会知道程序没有崩溃,并且可以去喝杯咖啡。

磁盘寻道被认为是有害的

磁盘寻道是你可能执行的最昂贵的操作之一。你可能无法从我们执行的寻道次数中知道这一点,但请相信我,它们确实很昂贵。因此,请避免以下次优行为

  1. 将许多小文件放置在整个磁盘上。

  2. 打开、状态和读取磁盘上所有的小文件

  3. 在不同的时间对上述文件进行操作,以确保它们被碎片化并导致更多的寻道。

  4. 在不同的目录中对上述文件进行操作,以确保它们位于不同的柱状组中并导致更多的寻道。

  5. 重复执行上述操作,而只需要执行一次即可。

你可以优化代码以使其对寻道友好的方法

  1. 将数据合并到一个文件中。

  2. 将数据保存在同一个目录中。

  3. 缓存数据,以便无需不断重新读取。

  4. 共享数据,以便每个应用程序加载时无需从磁盘重新读取。

  5. 考虑将所有数据缓存到一个二进制文件中,该文件经过正确对齐并且可以被 mmapped。

磁盘寻道的问题对于读取来说更加严重,不幸的是,我们正在执行读取操作。请记住,读取通常是同步的,而写入是异步的。这只会加剧问题,序列化每次读取,并导致程序延迟。