级别: 初级 |
Linux consultant, IBM
2005 年 7 月 04 日
要将 Linux™ C/C++ 程序从 x86 平台(Intel® 或 AMD)移植到 Linux on POWER™ 上,可以使用下面介绍的这些详细步骤。首先,我们来了解要为这种移植准备哪些内容;然后再遵循本文介绍的实现技巧就可以将您的 x86 平台的 Linux 上运行的代码移植到 POWER 平台上。
简介
通常,将 Linux 程序从 x86 平台移植到 Linux on POWER 上的过程非常简单,因为这两个平台都是基于 Linux 的。实际上,大部分移植只需要对某些编译器和链接器开关稍加修改并重新进行编译即可。
然而,当一个应用程序依赖于某种特定的硬件体系结构时,通常都需要进行比较大的修改。这份移植指南会重点介绍一下 Linux on x86 和 Linux on POWER 之间的区别,并提供了一些建议,让您可以将自己的 x86 代码移植到 Linux on POWER 平台上。
规划
当您将应用程序移植到一个新的平台上时,必须要正确地进行规划。要对移植进行充分的准备,您应该:
- 注册 IBM ®Chiphopper™ 计划。
- 理解 IBM eServer® POWER 平台的区别:POWER4™ 与 POWER5™ 之间。
- 确定要使用哪种 Linux for POWER 发行版:Red Hat Enterprise Linux 或 SUSE LINUX。
- 迁移到 GNU Make 编译系统。
- 理解 x86 和 POWER 体系架构之间的区别。
- 确定要使用哪种编译器:GCC(GNU Compiler Collection)或者 IBM XL C/C++ Advanced Edition for Linux。
这些任务都假设您的公司已经决定要将自己目前在 x86 平台上运行的应用程序移植到 Linux on POWER 平台上。如果您的情况并非如此,可能更想多了解一些有关 Linux on POWER 的特性和功能,那么您在阅读本文之前,就应该先参阅一下 “Linux on POWER:开发概览” (developerWorks,2005 年 3 月)。
注册 Chiphopper 计划
如果您的 Linux on x86 程序是用于商业用途的,而且代码是使用 C/C++、Java™ 或二者混合编写的,那么它可能就会是 IBM eServer Application Advantage™ for Linux(Chiphopper)程序的一个理想候选者。Chiphopper 提供了一些免费的工具和技术支持,可以帮助您很容易地实现 Linux on x86 上现有的程序到所有 IBM eServer 和中间件平台的移植、测试和技术支持。这可以帮助您最大限度地把握 Linux 市场机会,同时能够将成本控制到最低。有关 Chiphopper 计划的更多信息,以及要确定您是否具备申请资格,请参阅 参考资料 一节的内容。
如果您的应用程序并不满足 Chiphopper 计划的要求,或者您希望自己进行移植,那么可以继续阅读下一节的内容:理解 POWER 平台的区别。
理解 POWER 平台的区别
要移植到的 POWER 平台决定了可以使用哪些优化选项来编译程序。当使用 XL C/C++ 编译器时,这尤其重要,本文稍后就会详细讨论这个问题。
在 2004 年,IBM 发布了 POWER5 处理器,这是 POWER 家族处理器中最新的一代产品,所有 POWER 家族的处理器都是基于类似的基本架构的。除了具有以前版本的 POWER 芯片的优点之外,POWER5 处理器都具有内建的虚拟化能力,包括 IBM 的 Micro-Partitioning™(微分区)、DLPAR(Dynamic Logical Partitioning,动态逻辑分区)、虚拟存储、虚拟以太网和 CoD(Capacity on Demand)。有关 POWER5 虚拟化的更多信息,请参阅“Linux on POWER: An overview for developers”(developerWorks,2005 年 3 月)。
如果您要同时移植到基于 POWER4 和 POWER5 处理器的服务器上,可以在使用 XL C/C++ 编译器时对 -qarch
和 -qtune
使用不同的标记来对应用程序进行优化。到底应该使用哪些具体的标记以及合适使用这些标记将在本文稍后进行介绍。
确定使用哪个 Linux for POWER 发行版
SUSE LINUX Enterprise Server(SLES)for POWER 是 SUSE、IBM 和开源社区经过 4 年多努力的结晶。除了要为 POWER 架构提供 64 位的内核之外,SLES9 现在还包括一个 64 位的运行时环境,以及用来编译 64 位版本的流行开源软件(例如 Apache Web 服务器或 MySQL)所使用的 GCC 工具链。IBM XL 编译器是为对性能要求很高的应用程序准备的编译器,使用它以及 SLES9 所提供的 64 位 Linux on POWER 环境(包括 IBM eServer OpenPower™、IBM eServer p5、IBM eServer i5 和 IBM eServer BladeCenter™ JS20 已经),可以提供为开发和部署 Linux 解决方案提供一个空前优秀的平台。有关 SUSE LINUX 的更多信息,请参阅 参考资料 一节。
Red Hat Linux 在桌面和企业级的 Linux 市场方面都是被广泛认可的业界领导者。在最新的 Red Hat Enterprise Linux Advanced Server(RHEL4)中,Red Hat 在 V3 版本的基础上又提供了很多重要的技术改进。具体到开发领域来说,它包含了对安全性方面的改进,增强了服务器的性能和可扩展性,并增强了桌面应用的能力--所有这些都可以改进都可以确保与之前的版本保持高度的兼容性。RHEL4 是世界上领先的专注于企业级市场的 Linux 环境。有关 RHEL4 的更多信息,请参阅 参考资料 一节的内容。
决定是要使用 SLES 还是 RHEL,对移植过程并不会产生什么直接的影响。然而,要知道 SUSE 和 Red Hat 具有不同的发行版本,对于二进制兼容性也有不同的策略,这可能会影响到您长期的应用程序更新决策。
除了这两个 IBM 所支持的 Linux 发行版本之外,还有其他几个发行版本也可以在 Power 体系结构上运行,包括 Yellow Dog、Debian Linux 和 Gentoo(请参阅 参考资料)。
迁移至 GNU Make
如果您现在还没有使用 GNU Make 来编译程序,可以先考虑迁移到这上面来。使用一个工具来控制生成可执行程序,而不是依赖于一些脚本或直接与编译器进行交互来生成可执行程序,这是一个很好的编程实践。大部分 C 和 C++ 程序员都使用 Make 作为编译工具。切换到 GNU Make 上,让您可以在多个平台上使用相同的编译工具 makefile 确保编译操作都是一致的。GNU Make 在 SLES9 和 RHEL4 中都已经集成了。有关 GNU Make 的更多信息,请参阅 参考资料 一节的内容。
理解 x86 和 POWER 体系结构的区别
在将 x86 平台上的 Linux 应用程序移植到 POWER 架构上之前,您需要了解体系结构特有的几点区别。下面是这两种体系结构的一些区别--都是需要特别注意的一些问题:
- Endianness 或字节次序
- 32 位环境和 64 位环境中的数据类型的长度
- 体系结构中数据调整的区别
Endianness(字节次序)
POWER 处理器使用的是 big-endian 的体系架构,而 x86 处理器使用的是 little-endian 的体系架构。本节将介绍 endianness (也称为字节次序,byte ordering)的问题,并介绍处理这种问题的技术。开发者在将应用程序、设备驱动程序或数据文件从 x86 体系结构迁移到 POWER 体系结构上时,通常会碰到字节次序的问题。
Big endian 和 little endian
Endianness 是指数据元素及其各个字节是如何在内存中进行存储和寻址的。在一个多位组成的数字中,数量级越大的数字就认为是越重要的。例如,在一个 4 位数字 8472 中,4 就比 7 重要。同理,在多字节的数字数据中,该字节中所代表的数字越大,它也就越重要。例如,16 进制的数字 0x89ABCDEF 可以分成 4 个字节:0x89、0xAB、0xCD 和 0xEF,它们代表的数值分别为 0x89000000、0xAB0000、0xCD00 和 0xEF。显然,字节 0x89 的值最大;因此,它是最重要的值,而字节 0xEF 是最小的一部分,因此它最次要。
在将一个数字放到内存中时,从最低地址开始,只有两种选择:
- 首先放最不重要的字节(little endian)。
- 首先放最重要的字节(big endian)。
下图显式了 big-endian 和 little-endian 处理器是如何在内存中存放一个 32 位的 16 进制数值的,例如 0x89ABCDEF:
图 1. Big-endian 和 little-endian 处理器存放 16 机制的数值
0x89 是最重要的字节,0xEF 是最次要的字节。在 big-endian 的系统上,最重要的字节被保存在最低的内存地址中。在 little-endian 的系统中,最次要的字节被保存在最低的内存地址中。
如果一个程序将一个字写入内存,然后有从相同的位置作为一个字将其读出,那么字节次序就不是什么问题,因为在引用某个变量时,它看到的值是相同的。如果一个程序试图按照一个字节来读取相同的值(而该值是按照一个字写入的),那么根据处理器是 big endian 还是 little endian,所读取的值就可能有所不同。
POWER 处理器家族都使用 big-endian 数据布局,而 x86 处理器家族则都使用 little-endian 数据布局。在将 x86 程序迁移到 POWER 平台上时,确定哪些代码段是依赖于 endian 的,并将它们转换为 big-endian 等效的代码是非常重要的。
处理 endianess
本节将介绍如何判断代码中哪些部分是依赖于 endian 的,以及如何将这些代码转换成正确的 endian 格式。
依赖于 endian 的代码
对于数据的不一致引用是 C 语言的优点,这使得它在系统级软件的编程中非常通用,包括操作系统和设备驱动程序。这包括类型转换、指针操作、联合、位域、结构以及灵活的类型检查。然而,这些特性同时也是 endianness 移植问题的源头。我们可以考虑以下的代码:
|
清单 2. 使用联合引用不一致的数据
|
在 x86 系统上,结果如下:
|
在 POWER 系统上,结果如下:
|
endianness 的问题从表面上来看是由于 val
而引起的,它在被读取时,是一个字节一个字节从最重要的字节开始读取的。
虽然基于 POWER 处理器的系统可以支持 big-endian 的数据存储模型,但是有一个例外:其 I/O 总线。IBM Micro Channel® 以及最新的 PCI 都是基于 little-endian 的。在 POWER 系统中,I/O 控制器是系统总线与 I/O 总线之间的桥梁,它提供了一种功能,可以在从设备读写数据时,将这些数据在 little endian 和 big endian 格式之间进行转换。这种数据转换功能对于 Direct Memory Access(DMA)和 Program I/O(PIO)或 Memory-Mapped I/O(MMIO)数据全部适用。实际上,I/O 控制器会将数据作为字节流,这样系统中的字节 0 就可以对应 I/O 中的字节 0,字节 1 对应字节 1,依此类推。结果是,在移植与 I/O 无关的代码时,little-endian 的代码应该保持不变,因为 I/O 设备也是基于 little-endian 的。然而,我们建议您要确定所有依赖于 endian 的代码(这可以检查代码实现,也可以使用诸如 lint 之类的编程工具实现),并手工进行这种转换。
除了与 I/O 有关的程序之外,用来进行 TCP/IP 协议处理的程序可能也具有依赖于 endian 的代码。因为 TCP/IP 协议指定自己的数据格式是 big endian 的,而一个基于 x86 的程序在开始进行数学计算之前,可能会将 TCP/IP 数据转换成自己的 endianness 格式(little endian)。实际上,在 POSIX 标准(Portable Operating System Interface)中,已经定义了一些转换程序。这些程序包括 htonl()
、ntohl()
、htons()
和 ntohs()
。这些程序名字中的 s
代表 short,l
代表一个 32 位的数据。虽然调用这些函数的程序应该不会有什么 endianness 的问题,但是可能某些依赖于 endian 的代码会显式地自行操作 TCP/IP 数据。
编写 endian 中立的代码
如果一个程序能够移植到不同 endianness 的平台上并能维持自己的功能,那么它就被认为是 endian 中立的。换言之,其功能与所运行平台的 endianness 之间没有什么关系。以下是编写 endian 中立的代码的一些建议:
- 使用宏和原语
要使代码是可移植的,您可以使用宏和条件编译原语,如 清单 3 和 清单 4 所示。
|
清单 4. 使用宏来访问 4 字节整数的 16 到 23 位
|
- 使用编译时选项
实现这种功能一种比较好的方法是在编译器命令行中定义BYTE_ORDER
选项的值:-DBYTE_ORDER=BIG_ENDIAN
。这样在一个具有不同字节次序的新平台上编译时,就不用对设备驱动程序或应用程序的每个文件都进行编辑了。相反,您可以只编辑用来编译驱动程序或应用程序的 makefile 即可。
- 测试内存的布局
清单 5 中的例子对一个多字节整数的第一个字节进行了测试,从而确定它是 0 还是 1。如果它是 1,那么就认为所运行的平台是 little endian 的。这种方法的缺点是每次访问这种数据类型时,都要进行一次测试,然后向代码路径中添加其他的一些指令,这可能会对性能造成影响。应用程序或设备驱动程序的目标平台以及该平台所采用的 endianness,在编译时都是已知的。由于设备驱动程序和应用程序都有性能方面的考虑,因此使用编译时的定义是选择适当依赖于 endian 的代码段的最好的一种方法。
|
32 位环境和 64 位环境中数据类型的长度
Linux 操作系统上的 GCC 和 XL C/C++ 编译器都提供两种不同的编程模型:ILP32 和 LP64。ILP32 代表 integer/long/pointer 32,这是 Linux 上的 32 位编程环境。ILP32 数据模型提供了 32 位的地址空间,其理论内存上限是 4 GB。LP64 代表 long/pointer 64,这是 Linux 上的 64 位编程环境。表 1 给出了在 POWER 和 x86 平台中 ILP32 和 LP64 模型中基本数据类型的宽度。
表 1. POWER 和 x86 平台中 ILP32 和 LP64 模型中基本数据类型的宽度(位数)
POWER | x86 | |||
ILP32 | ILP64 | ILP32 | ILP64 | |
char | 8 | 8 | 8 | 8 |
short | 16 | 16 | 16 | 16 |
int | 32 | 32 | 32 | 32 |
float | 32 | 32 | 32 | 32 |
long | 32 | 64 | 32 | 64 |
long long | 64 | 64 | 64 | 64 |
double | 64 | 64 | 64 | 64 |
long double | 64/128* | 64/128* | 96 | 128 |
pointer | 32 | 64 | 32 | 64 |
* 在 Linux on POWER 中,long double 缺省为 64 位。如果您使用 XL C/C++ 编译器的
-qlongdouble
选项,可以将其设置为 128 位的。
所有有关数字类型的定义在 POWER 和 x86 平台上都可以在 /usr/include/limits.h 中找到。
现在大部分 x86 平台上的 Linux 应用程序都是以 32 位模式运行的。然而,随着 x86 64 位扩展的出现(x86-64),越来越多的程序将会直接编写为 64 位的。在将 x86 应用程序移植到 POWER 平台上时,您应该使用与源环境匹配的 Linux POWER 环境。换言之,在迁移到 POWER 平台上的过程中,要避免从一个 32 位编程模型迁移到 64 位的编程模型,因为这种尝试并不是单纯的移植,而是一次开发任务。如果您将一个 32 位的 x86 应用程序移植到 64 位 POWER 编程模型中,那么可以将这次迁移分为两个步骤:
- 移植到 Linux on POWER 的 32 位环境中(包括测试和验证)。
- 迁移到 64 位环境中。
这就是说,如果一个程序可以满足以下条件,就应该被移植到 64 位环境中:
- 可以利用超过 4 GB 的虚拟地址空间。
- 可以利用更多的物理内存(超过 4 GB),如果用户希望将其部署到一个具有多于 4 GB 物理内存的系统上。
- 可以利用 64 位的 long integer 类型。
- 可以利用全部的 64 位寄存器实现更有效的 64 位数学计算。
- 使用大于 2 GB 的文件。
可以从迁移到 64 位程序中获益的一些程序包括:
- 数据库应用程序,尤其是那些进行数据挖掘的程序
- Web 缓存和 Web 搜索引擎
- CAD/CAE 模拟和建模工具的组件
- 科学计算程序,例如流体力学、遗传仿真
一个程序可以保持是 32 位的,但仍然可以在 64 位的 Linux on POWER 内核上运行,而不需要修改代码。IBM 基于 POWER 处理器的服务器上的 Linux 可以支持在 64 位体系结构上同时运行 32 位和 64 位的应用程序,而不会造成这两种模式的性能降低,这是因为 64 位的 POWER 体系结构中包含了对本地 32 位模式的完全支持。
在不同的平台(从 x86 到 POWER)或编程模式(从 ILP32 到 LP64)之间移植程序时,您应该考虑不同环境中数据宽度和对齐设置的不同,这样才能防止出现可能的性能降低或数据崩溃的问题。
在从 x86 ILP32 移植到 POWER ILP32 上或从 x86 LP64 移植到 POWER LP64 上时,注意在 表 1 中,所有的基本数据类型的宽度都是相同的,long double 例外,对于 64/128 位的 IPL32 来说,它是 96 位;而对于 64/128 位的 LP64 来说,它是 128 位。这意味着您应该尽可能地检查以下代码中与 long double 数据类型有关的地方。如果您计划要使用 XL C/C++ 编译器,请使用 -qlongdouble
编译标记来保证 long double 数据类型具有最好的可移植性。
数据对齐
在不同的平台或 32 位和 64 位模式之间移植程序时,要考虑不同环境中对齐设置的不同,从而避免出现可能的性能降低和数据崩溃的问题。最好的实践方法是确保数据都是自然对齐的。自然对齐的意思是说将数据项存储在是其大小的整数倍的地址处(例如,8 字节的数据的地址就应该是 8 的整数倍)。对于 XL C/C++ 编译器来说,聚集类型(C/C++ 结构/联合和 C++ 类)中的每种数据类型都会根据 linuxppc 或位填充规则按照字节边界进行对齐,其中 linuxppc 是缺省值,它是自然对齐的。linuxppc 也是与缺省的 GCC 对齐规则兼容的。表 2 显式了 POWER 和 x86 上的对齐值,以及它们的数据类型的宽度(以字节为单位)。
POWER | x86 | |||||||
ILP32 | ILP64 | ILP32 | ILP64 | |||||
宽度 | 对齐 | 宽度 | 对齐 | 宽度 | 对齐 | 宽度 | 对齐 | |
char | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
short | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
int | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 |
float | 4 | 4 | 4 | 4 | 4 | 4 | 4 | 4 |
long | 4 | 4 | 8 | 8 | 4 | 4 | 8 | 8 |
long long | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 |
double | 8 | 8 | 8 | 8 | 8 | 8 | 8 | 8 |
long double | 8/16 | 8 | 8/16 | 8 | 12 | 4 | 16 | 16 |
pointer | 4 | 4 | 8 | 8 | 4 | 4 | 8 | 8 |
GCC 和 XL C/C++ 中的关键字 __alignof__
让您可以了解一个对象是如何对齐的。它的语法与 sizeof
类似。例如,如果目标及其要求一个 double 类型的值按照 8 字节边界进行对齐,那么 __alignof__
(double) 就是 8。
正如在 表 2 中介绍的一样,long double 类型的变量在 x86 平台上是按照 4 个字节进行对齐的,而在 POWER 平台上则是按照 8 个字节进行对齐的。因此,在不同的平台上,这种结构就有不同的布局。不要将大小和偏移量都在编码中写死了,这一点非常重要。相反,使用 C 语言中的 sizeof
操作可以查询基本类型和复杂类型的大小。宏 offsetof
是一个变量,它可以获取结构程序从该结构开始地址处的偏移量。
确定使用哪种编译器:GCC 或 IBM XL C/C++
在 Linux on POWER 上有两种 C/C++ 编译器:GCC 和 IBM XL C/C++。GCC 为那些在 Linux 上编译的代码提供了很好的可移植性,而 IBM XL 编译器与 GCC 相比,则提供了很好的性能改进,它可以使用更高级的优化。这两种编译器都提供了 32 位和 64 位的编译模式,Linux on POWER 环境允许同时运行 32 位和 64 位的代码,而不会降低程序的性能。
使用 GCC 编译器进行移植
对于在多种 GCC 编译器就是原始编译器的平台上开发的项目来说,GCC 编译器通常被用来为 Linux on POWER 部署应用程序。在性能不太关键的情况中,例如一些小工具,这都没有什么问题。GCC 可以使用一些只有 GCC 才理解的代码原型,例如 GCC 特有的宏。然而,这些 GCC 特有的特性中有很多也都已经集成进到 XL C/C++ 编译器中了。
通常,使用 GCC 来迁移代码应该比较简单。在大部分情况中,这只需要简单地重新编译一下即可,也就是之需要键入 make
命令即可。体系结构可能会有所区别,偶然可能会出现库程序的版本不同的问题。但是对于大部分情况来说,在哪种体系结构上运行并无关紧要。体系结构特有的标记,例如 -m486
和 -mpowerpc64
并鼓励在 Linux on POWER 的编译过程中采用,因为 GCC 没有这么丰富的处理器映射来对这些体系结构上的程序进行优化。而且,不使用体系结构特有的标记,在不同的 POWER 硬件模型之间就能保证更好的可移植性。
在 SLES9 和 RHEL4 中也包含了 64 位的 GNU 编译器,推荐您使用这些 Linux 发行版本中所提供的编译器。
在所有的体系结构中,库都必须使用 -fPIC
标记进行编译;在 x86 平台上,GCC 会缺省就应用这个标记。在 POWER 平台上,这个标记规定所生成的代码必须在一个共享对象中使用。更多信息请参阅 GCC 编译器手册(请参阅 参考资料)。
使用 IBM XL C/C++ 编译器进行移植
IBM XL C/C++ V7.1 是 IBM VisualAge® V6.0 for Linux 的下一个发行版本。XL C/C++ 编译器可以作为 GCC 的一种高性能的替代品,同时它还提供了很多其他的特性。
幸运的是,XL C/C++ 使用的是 GNU C 和 C++ 的头文件,所生成的应用程序也是被链接到 GCC 所提供的 C 和 C++ 运行时库上。这意味着 XL C/C++ 编译器会产生 GNU 的 elf 对象,后者是与 GCC 编译器所生成的对象完全兼容的。XL C/C++ 预装了 SMP 运行时库来支持 XL C/C++ 编译器的自动并行处理和 OpenMP 特性。
从 GCC 迁移到 XL C/C++ for Linux on POWER 上非常简单。XL C/C++ 可以通过提供一个选项 -qinfo=por
来帮助实现这个任务,从而帮助您对所产生的诊断消息进行过滤,只显示那些与移植性有关的问题。另外,XL C/C++ 也可以支持 GNU 对 gcc 和 gcc-c++ 扩展的一个子集。有关 XL C/C++ 所支持的特性的完整列表,以及哪些语法可以接受,哪些语法会被忽略,请参阅“XL C/C++ for Linux on pSeries Compiler Reference”。
要在 C 代码中使用这些支持特性,请指定 -qlanglvl=extended
或 -qlanglvl=extc89
选项。在 C++ 中,所有支持的 GNU gcc/gcc-c++ 特性缺省都是可以接受的。此外,gxlc 和 gxlc++ 可以用来帮助在使用 GNU 编译器编译现有应用程序时,对 makefile 所做的修改可以降至最小。
XL C/C++ 文档
在安装 XL C/C++ 时,会提供以下的 PDF 文档:
- “XL C/C++ for Linux Getting Started”(getstart.pdf)。
- “XL C/C++ for Linux Installation Guide”(install.pdf),这份文档中包含了有关安装编译器和使用手册页的用法说明。
- “XL C/C++ for Linux C/C++ Language Reference”(language.pdf),这份文档中包含了有关 IBM 所支持的 C 和 C++ 语言的信息。
- “XL C/C++ for Linux Compiler Reference”(compiler.pdf),这份文档中包含了有关各种编译器选项、程序、宏和内嵌函数的信息,包括它们对于并行处理的用法。
- “XL C/C++ for Linux Programming Guide”(proguide.pdf),这份文档中包含了(其他文档中都未曾介绍过的)有关使用 XL C/C++ 进行编程的信息。
这些文档可以在下面的地方找到:
- 安装 CD 的 /docs/LANG/pdf 目录,其中 LANG 标识语言和位置编码
- 安装之后位于 /opt/ibmcmp/vacpp/7.0/doc/LANG/pdf 目录
另外还有一个 HTML 版本的产品文档被安装到 /opt/ibmcmp/vacpp/7.0/doc/LANG/html 目录中。在这个目录中,打开 index.html 文件就可以查看 HTML 文件的内容。
XL C/C++ 中的优化选项
XL C/C++ 为 IBM 硬件提供了很多优化选项。对于 Linux on POWER 来说,很多使用 XL C/C++ 编译的程序的性能都比使用 GCC 编译的程序有显著的提高。注意并不是所有的优化对于所有的应用程序都是有益的。通常在编译器所实现的优化级别与编译时间的增长以及调试能力的减弱之间是一种平衡。
优化级别
优化级别是由编译器选项指定的。下表对编译器在每种优化等级下的行为进行了总结:
选项 | 行为 |
-qnoopt | 快速编译,完全调试支持 |
-O2(与 -O 相同) | 执行编译器开发人员认为是编译速度和运行时性能最佳组合的优化。如果没有使用 -qnostrict_induction 或 -qnostrict 明确否定,那么这个设置中将包含 -qstrict 和 –qstrict_induction |
-O3 | 执行内存占用大、编译时间长或两者都有的其他优化。当运行时改善比最大程度地减少编译资源使用重要时,建议使用这些优化 |
-O4 和 -O5 | 执行过程间优化、循环优化和自动计算机调整 |
目标机器选项是那些可以引导编译器为某种给定的微处理器或体系结构家族生成优化代码的选项。通过选择适当的目标机器选项,您可以对不同范围的目标机器进行合适的优化:这可以是一组选定的目标处理器,一个给定家族的处理器体系结构中的一组处理器,或者某个特定的处理器。以下选项可以控制影响特定目标机器优化措施:
表 4. 影响特定目标机器的优化选项选项 | 行为 |
-qarch | 选择应该为其生成指令代码的处理器架构系列。默认值是 -qarch=ppc64grsq 。还可以使用以下子选项: auto、 pwr3、 pwr4、 pwr5、 ppc970、 ppc64、 ppcgr、 rs64b、 rs64c |
-qtune | 偏向于对给定微处理器上的执行操作进行优化,但这并不意味着将与指令集合架构有关的任何操作作为目标。Linux 上的默认值是 -qtune=pwr3 。可用的子选项包括: auto、 pwr3、 pwr4、 pwr5、 ppc970、 rs64b、 rs64c |
-qcache | 定义特定缓存或内存布局。如果使用了 -qcache ,则将 -qhot 或 -qsmp 与其一起使用 |
-qhot | High-Order Transformations:该优化可以通过诸如交换、合并及展开等方法特别地提高循环性能。指定 -qhot 时,默认值为选项 -qhot=vector 。尝试将 -qhot 与 -O2 和 -O3 一起使用。它被设计用来在没有机会进行这种转换时,也具有很自然的效果 |
-qsmp | 生成共享内存并行处理所需的线程代码。指定 -qsmp 时,默认值为选项 -qsmp=auto 。如果在 OpenMP 程序中编译且不想进行自动并行化,则使用 -qsmp=omp:noauto 。使用 -qsmp 时,总是使用 _r 编译器调用 |
要获得最好的目标机器选项,您应该:
- 使用
–qarch
选项指定您期望代码能够运行良好的最小系列的机器。 - 使用
–qtune
选项指定在哪种机器上的性能最好。例如,如果您的应用程序只支持 POWER5 系统,就可以使用-O3 -qarch=pwr5 -qtune=pwr5
选项。修改缓存布局在某些情况中可能会非常有用,例如系统具有可配置的 L2 和 L3 缓存.或者整型模式会减小缓存共享级别的有效大小(例如,POWER5 中双核芯片的 SMP)。如果您的程序只要在 POWER4 上运行,或者要在 POWER4 和 POWER5 上运行,可以使用-qarch=pwr4 -qtune=pwr4
。
POWER 平台可以支持其他平台上所没有的一些指令。XL C/C++ 提供了一组内嵌的函数,它们可以直接映射为特定的 POWER 指令。使用这些函数可以消除函数调用返回、参数传递、堆栈调整以及其他与函数调用有关的开销。有关所支持的内嵌函数的完整列表,请参阅“XL C/C++ C++ for Linux on pSeries Compiler Reference”安装文档。
然而,那些最初是想使用 GCC 编译器进行编译的程序在使用 IBM XL C/C++ 编译器进行编译时需要多加注意。您需要编辑 makefile 来体现 XL C/C++ 编译器的正确路径,它缺省位于 /opt/ibmcmp/ 中。您还需要为特定的体系结构设置正确的优化标记(例如对于 IBM POWER5 来说是 -03 –qarch=pwr5 –qtune=pwr5
)。
除了对各种 POWER 体系结构进行优化的优化选项之外,XL C/C++ 编译器还可以使用通用的模式来编译软件。这可以保证在所有的 POWER 体系结构上程序都是兼容的,代价是性能不如对具体某种体系结构进行优化后的结果好。在编译 64 位的代码时,编译器必须要为 64 位的编译给出一个标记(即 -q64
),因为它缺省是 32 位模式。
下面是使用 XL C/C++ 编译器来编译面向 GCC 的代码时的一些技巧:
- C++ 注释:GCC 缺省允许在 C 文件中使用 C++ 风格的注释,但是在 XLC(XL C 编译器)中则并非如此。因为要对源代码中所有的注释进行修改以便它们可以遵守 C 风格的注释,这并不经济,因此 XLC 提供了一个
-q
参数允许使用这种注释:-q cpluscmt
。当使用这个标记编译 C 代码时,C 和 C++ 风格的注释都会进行解释。 - 环境变量通常是用来配置编译脚本最简单的方法。您无需手工编辑配置脚本和 makefile,而是应该设置适当的环境变量,例如
$CC 和 $CFLAGS
,这样就可以让配置脚本生成 makefile,而不需要手工进行编辑。 - 配置脚本还需要注意适当的平台类型。这可能是 linux-powerpc-unknown-gnu 或 linux-powerpc64-unknown-gnu。您应该通过为配置脚本添加一个
-target=
标记来为 GCC 或 XL C/C++ 设置这个编译选项。 - 虽然对于 IBM XL C/C++ 编译器来说已经有非常丰富的文档了,但是不加参数运行编译器命令,例如
$COMPILER_PATH/bin/cc
就可以得到完整的参数列表。
GCC 和 XL C/C++ 编译器选项的比较
下 表 对 GCC 和 XL C/C++ 常用的编译器选项进行了比较:
GCC | XL C/C++ C/C++ | 说明 |
-v | -v, -V, -# | 开启详细模式 |
-p/-profile | -p | 设置编译器生成的对象文件进行概要分析 |
n/a | -q32, -q64 或设置 OBJECT_MODE 环境变量 | 创建 32 位或 64 位对象。GCC 64 位编译器位于 /opt/cross/bin 中 |
-fsyntax-only | -qsyntaxonly | 执行语法检查,不生成对象文件 |
-fpic | -qpic=small | 生成共享库中使用的 Position-Independent Code。在 XL C/C++ 中,Global Offset Table 的大小不超过 64 Kb。如果指定 –qpic ,而无任何子选项,则假设 -qpic=small 。如果指定了 -qmkshrobj 编译器选项,则启用 –qpic 选项 |
-fPIC | -qpic=large | 允许 Global Offset Table 大于 64 Kb |
-pthread | -qthreaded or _r invocation mode | 创建在多线程环境中运行的程序 |
-fno-rtti | -qnortti | 对于异常处理和 typeid 和 dynamic_cast 操作符的使用,禁止生成运行时类型 –qrtti 识别(RTTI)。在 XL C/C++ 中,默认值为 -qnortti |
-static | -qstaticlink | 使用这个选项生成的对象将无法与共享库进行链接 |
-static-libgcc | -qstaticlink=libgcc | 指示编译器与 libgcc 的静态版本链接 |
-shared | -qmkshrobj | 指示编译器生成共享对象 |
-shared-libgcc | -qnostaticlink=libgcc | 指示编译器与 libgcc 的共享版本链接 |
-Wl,-rpath | -Wl,-rpath 或 –R | 传递用冒号分隔的目录列表,用它来指定运行时链接程序搜索的目录 |
-fno-implicit-templates, -frepo | -qtempinc, -qtemplateregistry, -qtemplaterecompile | 模板实例化 |
-w | -w | 取消警告消息 |
-warn64 | 允许对长型到整型的截断舍位(long-to-integer truncation)进行检查 | |
-qinfo=<…> | 生成信息消息 | |
-fpack-struct | -qalign=bit_packed | 使用 bit_packed 排列规则 |
-qalign=linuxppc | 使用默认 GCC 排列规则来维护与 GCC 对象的兼容性。这个值是默认值 | |
-O,-O2,-O3 | -O,-O2,-O3,-O4,-O5 | 优化级别 |
-qarch, -qtune, -qcache | 特定处理器的优化选项 |
请参阅“How to use IBM XL C/C++ Advanced Edition V7.0 for Linux on POWER: A guide for GCC users”(developerWorks,2004 年 10 月)中有关 IBM C/C++ 编译器的更多信息。
移植
在完成规划的所有步骤之后,您就应该准备好开始进行移植了。本节将简要讨论成功将程序移植到 Linux on POWER 上的一些推荐步骤。
- 将编译系统迁移到 GNU Make 上(如果需要)
这是构建一个或多个 makefile 的过程。您也可以利用 GNU 的自动编译工具,例如 Autoconf、Automake 和 Build 工具,从而最大程度地保证程序在不同 UNIX® 平台之间的可移植能力。GNU 的自动编译工具位于 directory.fsf.org/devel/build/ 中。
- 修改依赖于体系结构的代码(如果需要)
考虑字节次序(endianness)、32 位和 64 位模式中的数据长度以及不同平台上数据对齐的问题,这在 理解 x86 和 POWER 体系结构之间的区别 一节中介绍过了。
- 编译
在构建 makefile 并对所有的程序进行修改之后,编译过程就非常简单了,这只是执行一个命令而已,例如make
。如果您在编译过程中碰到了错误,那么通常都是编译器的或链接器的错误,或者是程序的语法错误。通常通过 makefile 修改编译器选项或修改代码中的语法都可以修复这些错误。编译器的参考手册和编程指南是这个过程中最好的参考资料。对于 IBM XL C/C++ 来说,请参考“XL C/C++ for Linux Compiler Reference”(compiler.pdf)和“XL C/C++ for Linux Programming Guide”(proguide.pdf)。对于 GCC 来说,编译器的参考手册和编程指南都可以在 gcc.gnu.org/onlinedocs 中找到。
- 测试和故障诊断
在成功编译好程序之后,要对其进行测试是否存在运行时的错误。运行时错误通常都是与这个步骤中的程序逻辑有关的。通常编写几个测试程序来验证程序的输出如您所期望的一样是个好主意。
- 性能调优
现在所移植的代码已经可以在 POWER 平台上运行了,对其进行监视,以确保它的执行情况符合我们的预期。如果不符合,就需要彻底进行性能调优。您可以使用以下的工具来判断应用程序中的问题,并显示应用程序与 Linux 内核之间的交互:
- OProfile
OProfile 会根据与硬件相关的事件对代码进行分析,例如缓存命中率或 CPU 周期。例如,OProfile 可以帮助您判断哪些源代码中的程序引起了缓存的不命中。OProfile 利用了很多 CPU 中所提供的硬件性能计数器,包括 IBM POWER4、POWER5 和 PowerPC™ 970。有关 OProfile for Linux on POWER 的更多信息,请参阅“Identify performance bottlenecks with OProfile for Linux on POWER”(developerWorks,2005 年 5 月),或访问 OProfile 的 Web 站点(请参阅 参考资料)。
- Post-Link 优化
Post-Link 优化工具可以在典型的负载情况中运行程序时搜集程序的行为信息,从而对程序的执行影响进行优化。然后再对程序重新进行分析(同时使用所搜集到的 profile),并应用全局优化选项(包括程序重构),并为这种负载情况创建一个新版本的优化后的程序。与原有的程序相比,优化器所生成的新程序通常运行的速度更快,并且使用的物理内存也更少。更多信息,请访问 Post-Link 优化的 Web 站点(请参阅 参考资料)。
- TProf
TProf 是一个定时的 profiler,它可以判断在用户指定的时间间隔内哪些代码正在 CPU 上运行。它可以用来报告应用程序和内核中的热点区域。TProf 记录了在每个系统时钟中断时(每个 CPU 每秒 100 次)哪些代码正在运行。
- PTT
PTT 搜集每个线程的统计信息,例如 CPU 循环的次数、中断次数以及该线程被调度的时间。
- AI
AI 显式在用户指定的时间间隔那 CPU 的使用情况统计信息。
- OProfile
- Package
如果您移植的程序是一个商业产品,或者您希望将程序分发给第三方进行安装,那么您可能需要对所移植的程序进行打包,包括库和文档,有时还包括源代码。Linux 提供了几种方法来对应用程序进行打包,例如 tarball、自安装的 shell 脚本和 RPM。RPM 是 Linux 上最流行的一种打包工具。有关 RPM 的更多信息, 请参阅 参考资料 一节的内容。
图 2. 上面介绍的移植操作 6 个步骤中第一个步骤的流程
结束语
Linux on POWER 提供了一个企业级的 Linux 环境,以及完整的 32 位和 64 位的应用环境和工具链。Linux on POWER 提供了两套编译器集,它们可以简化开源代码的移植,并促进广受好评的 POWER 体系结构这种高性能的平台的采用。将您的 x86 平台上的程序移植到 Linux on POWER 上,您就可以在 POWER 架构上充分利用程序的性能,现在以及在 Linux 操作系统上提供的一些前所未有的工具。总之,Linux on POWER 是部署高性能的 Linux 程序的业界领先的平台。
文章来源于领测软件测试网 https://www.ltesting.net/