分享

Lesson 26. Optimization of 64

2022-05-22  兰亭文艺

课 26. 64 位程序的优化

2013 年 8 月 19 日

减少消耗的内存量

当程序在 64 位模式下编译时,它开始消耗比 32 位版本更多的内存。这种增加通常不会被注意到,但有时内存消耗可能会增长两倍。内存消耗的增长由以下因素决定:

  • 更大的内存量来存储一些对象,例如指针;
  • 结构中数据对齐规则的变化;
  • 堆栈内存消耗的增长。

我们通常可以容忍主内存消耗的增长——64 位系统的优势是用户可用的内存量非常大。如果一个程序在具有 2 GB 内存的 32 位系统上占用 300 MB,而在具有 8 GB 内存的 64 位系统上占用 400 MB,这是完全可以的。在相对单位中,这意味着程序在 64 位系统上占用的可用内存要少三倍。因此,反对我们所描述的内存消耗的增长是不合理的 - 添加更多的内存更容易。

但这种增长有一个缺点。它与性能损失有关。尽管 64 位程序代码更快,但从内存中提取大量数据可能会抵消所有优势,甚至会降低性能。在内存和微处理器(高速缓存)之间传输数据的操作并不便宜。

减少内存消耗的方法之一是优化我们在第 23 课中介绍过的数据结构。

另一种节省内存的方法是使用更多的节省数据类型。例如,如果我们需要存储大量整数并且我们知道它们的值永远不会超过 UINT_MAX,我们可以使用类型 'unsigned' 而不是 'size_t'。

在地址算术中使用 memsize 类型

在地址算术中使用ptrdiff_tsize_t类型可能会给您带来额外的性能提升,同时使代码更安全。例如,使用大小与指针容量不同的类型int作为索引会导致二进制代码中出现额外的数据转换命令。我们谈论的是 64 位代码,其中指针的大小为 64 位,而int类型的大小保持不变 - 32 位。

给出一个简短的例子来证明size_tunsigned更好并不容易。为了不偏不倚,我们必须使用编译器的优化功能。但是优化代码的两种变体通常会变得太不同而无法轻松展示它们的差异。我们仅通过第六次尝试就成功地创建了一个简单的示例。但是该示例仍然远非理想,因为它表明 - 而不是上面讨论的数据类型的不必要转换 - 编译器可以在使用size_t时构建更有效的代码这一事实。考虑以相反顺序排列数组项的程序代码:

unsigned arraySize; ... for (unsigned i = 0; i < arraySize / 2; i++) { float value = array[i]; array[i] = array[arraySize - i - 1]; array[arraySize - i - 1] = value; }

示例中的变量 'arraySize' 和 'i' 的类型为unsigned您可以轻松地将其替换为size_t并比较图 1 中所示的一小段汇编代码。

26_优化/image1.png

图 1 - 使用 unsigned 和 size_t 类型比较 64 位汇编代码片段

当使用 64 位寄存器时,编译器设法构建了更简洁的代码。我们不想说使用unsigned类型(左侧的文本)创建的代码会比使用size_t类型(右侧的文本)创建的代码慢。比较当代处理器上的代码执行速度是一项相当困难的任务。但是您可能从示例中看到,编译器在使用 64 位类型时可以构建更简洁和更快的代码。

现在让我们考虑一个示例,从性能的角度展示ptrdiff_tsize_t类型的优势。出于演示的目的,我们将采用一个简单的算法来计算最小路径长度。您可以在这里看到完整的程序代码

函数FindMinPath32以经典的 32 位风格编写,具有无符号类型。函数FindMinPath64与它的不同之处仅在于其中的所有无符号类型都替换为size_t类型。没有其他区别!我想你会同意它不能被认为是对程序的复杂修改。现在让我们比较这两个函数的执行速度(表 1)。

26_优化/image2.png

表 1 - FindMinPath32 和 FindMinPath64 函数的执行时间

表 1 显示了在 32 位系统上相对于函数FindMinPath32的执行速度减少的时间。这样做是为了清晰。

第一行函数FindMinPath32的运算时间在 32 位系统上为 1。这是因为我们把这个时间作为一个计量单位。

在第二行中,我们看到函数FindMinPath64的运算时间在 32 位系统上也是 1。难怪,因为unsigned类型与32 位系统上的size_t类型一致,函数FindMinPath32FindMinPath64之间没有区别。小偏差 (1.002) 仅表示测量中的小误差。

在第三行中,我们看到了 7% 的性能提升。在为 64 位系统重新编译代码后,我们完全可以预料到这个结果。

第四行是我们最感兴趣的。性能增益为 15%。这意味着仅使用size_t类型而不是unsigned类型,我们就可以让编译器构建更有效的代码,其运行速度甚至快 8%!

这是一个简单而明显的例子,说明不等于机器字大小的数据如何降低算法性能。仅仅ptrdiff_tsize_t替换intunsigned类型可能会带来显着的性能提升。它首先涉及这些数据类型用于索引数组、地址算术和安排循环的情况。

笔记。虽然静态分析器PVS-Studio并不是专门为优化程序而设计的,但它可以帮助您进行代码重构,从而使代码更加高效。例如,在修复与地址算术相关的潜在错误时,您将使用memsize-types,因此允许编译器构建更优化的代码。

内在函数

内在函数是特殊的系统相关函数,它们执行那些在 C/C++ 代码级别无法执行的操作,或者更有效地执行这些功能。实际上,它们可以让您摆脱 inline-assembler,因为它通常是不可取的或不可能使用的。

例如,可用函数列表等提供这些函数列表程序可以使用内部函数来创建这些代码。大小当然会大一些

Visual C++ 有一个选项“/Oi”,可以让微软自动调用某个特殊函数的替代器的相似性。

除了使用其代码如下显式自动替换常用功能外,还可以在以下式使用常用功能。由于这个因素,这可能会有所帮助:

  • 在64位模式下,编译器Visual C++不支持内部联编程序,而内部代码支持。
  • 因为他们需要支付和其他类似的低级结构的知识。
  • 所有代码在编译器中更新,而必须手动更新。
  • 压缩器最好使用代码。
  • 此代码比代码更容易移植。

在自动使用内部的一些功能(在使用内部的帮助下)的模式下,让您获得免费使用的背景。

要了解有关使用内部函数的更多信息,请参阅Visual C++ 团队的博客

结盟

在此操作下,通过手动定义来帮助编辑器提高性能的情况。

// 16-byte aligned data
__declspec(align(16)) double init_val[2] = {3.14, 3.14};
// SSE2 movapd instruction
_m128d vector_var = __mm_load_pd(init_val);

来源“在AMD64架构的Windows上移植和优化的多媒体应用程序”解码器非常好,在AMD64架构的64位Windows上移植和优化的应用程序“非常适合”这些。

其他提高性能的方法

要了解有关优化 64 位应用程序问题的更多信息,请参阅文档“ AMD64 处理器的软件优化指南”。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 全屏 打印 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多