在 Linux on POWER 上利用透明大内存页

豆豆网   技术应用频道   2007年08月22日  【字号: 收藏本文

内容摘要:尽管大量支持 Linux 大内存页的硬件平台也都支持 libhugetlbfs,但本文只侧重于介绍基于 IBM POWER 处理器的系统上的 16MB 大内存页支持。

  通过本文,了解更多有关 libhugetlbfs 库以及如何将这些库与 GNU Compiler Collection (GCC) 或 IBM XL C/C++ 及 XL Fortran compilers for Linux® 结合使用的信息。libhugetlbfs 是一个开源社区项目,可为客户应用程序提供到系统大内存页的透明访问。SUSE Linux Enterprise Server 10 (SLES 10) 和 Red Hat Enterprise Server Linux 5 (RHEL 5) 现均支持 libhugetlbfs。尽管大量支持 Linux 大内存页的硬件平台也都支持 libhugetlbfs,但本文只侧重于介绍基于 IBM POWER 处理器的系统上的 16MB 大内存页支持。

  简介和背景

  透明地利用 Linux® 上的大内存页会使内存页面表项能够覆盖更大范围内(多至数兆字节)的连续物理内存,有了最近推出的 SourceForge 的 libhugetlbfs 库版本 1(参见 参考资料),这一切就变得更加容易。libhugetlbfs 库已经针对 SUSE Linux Enterprise Server 10 (SLES 10) 作了更新并可用于 Red Hat Enterprise Server Linux 5 (RHEL 5)。客户可以利用它进行基准测试活动以改进运行 Linux 的 POWER、Intel® 和 AMD 系统上的特定应用程序的性能。本文只侧重于具有 16MB 页面大小的、基于 IBM POWER 处理器的系统,简要介绍了 libhugetlbfs,具体包括的内容如下:

  何时应该及何时不应该使用 libhugetlbfs

  如何安装和设置 libhugetlbfs

  如何提供到大内存页的系统访问控制

  一个简单的代码示例,用大内存页备份 malloc 和应用程序 bss 段。

  使用了 libhugetlbfs 的行业标准的外部发布的示例

  所支持的系统

  对于 Linux on POWER 系统而言,具有 16MB 页面的 SLES 10 和 RHEL 5 系统均支持 libhugetlbfs 库,比如 POWER4、POWER5、POWER5+ 系统和 BladeCenter® JS20 和 BladeCenter JS21。

  虽然本文只侧重 POWER 系统,但是支持大内存页的、基于 Intel 和 AMD 的 Linux 系统也提供对 libhugetlbfs 的支持。对于某些系统,虽然硬件页面的大小不同,但方式类似,而且显示某些良好性能改进的测试也都可以在这些 Linux 系统上看到。

  对应用程序无需进行任何源代码更改

  在本文中,透明地利用大内存页 指的是应用程序可以利用较大的硬件页面大小的性能优势,而无需进行任何源代码更改。虽然 Linux 支持利用系统大内存页,但应用程序必须进行特定的编码才能利用该特性。这类支持存在于常见的一些系统软件产品中,比如最新的一些 Java™ 引擎和各种大型数据库提供商的产品。

  如果是在具有上述平台的 32 位或 64 位应用程序上使用 libhugetlbfs,则应用程序无需进行任何代码更改就可以利用 16MB 大内存页。可选的使用方式有多种,包括:

  用大内存页备份 .bss、.data 和 .text 段:可以指定将所有三个段(.bss 段、.data 段 和 .text 段)均加载进大内存页。.bss 段是程序中的一个未初始化的全局数据结构(比如一个大型的 Fortran 数组)。 .bss 段可以很大,可以有许多声明和使用 Fortran 数组的计算密集型工作负载,而这些数组可能会使用数兆字节的内存空间。.data 段是一个应用程序初始化了的全局变量和数据结构(通常不大),.text 段是可执行文本(二进制代码)。为了指定这些应用程序段由系统大内存页备份,所需做的就是重新链接执行体。

  只备份 .bss 段:在 Fortran 程序中普遍采用的一种 libhugetlbfs 用法是只指定 .bss 段由系统大内存页备份。.data 段和 .text 段则继续用常规的硬件基本页面大小进行备份。与第一种方法类似,使用 libhugetlbfs 只需进行执行体的简单重链接。

  用大内存页备份 malloc:可以指定让运行时 malloc 调用来使用由 16MB 页面备份的内存。这也包括 malloc 的派生词(calloc、 valloc、realloc 等等),原因是底层的 malloc 调用是被重写的系统调用。libhugetlbfs 库自动在 16MB 页面中管理所有的 malloc 请求。可以将这种方式用于任何执行体,而不必是经特殊链接的执行体。

  关键的几点考虑

  在开始实际使用 libhugetlbfs 时,有如下几个关键点需要考虑:

  为了开发受客户环境支持的应用程序,您需要一个受支持的 Linux 发行版。对于 SUSE,就是 SLES 10。对于 Red Hat,就是包括 libhugetlbfs 库的 Red Hat Enterprise Linux 5 (RHEL 5)。本文无意介绍 SLES 9 和 RHEL 4,原因是它们不具有对 libhugetlbfs 适当的内核支持。虽然有经验的 Linux 用户总是可以获得能提供所需 libhugetlbfs 内核功能的最新的主流内核,但这并不是一种受客户支持的配置。

  Linux on POWER 上的大内存页是在使用(或在 boot 之上使用或由 root 用户使用)之前定义的 16MB 大内存页,这些大内存页会在被使用时由操作系统在主内存中固定。POWER 系统上的 16MB 大内存页是连续的 16MB 物理内存块。

  对于 POWER 系统而言,Linux 社区将这些内存页称作 “huge page”,而传统的 AIX® 和 IBM POWER 硬件团队则将这些内存页称为 “large page”。由于社区和业界的术语一直模糊不清,因此我建议用所使用页的实际大小来加以区别。在本文中,我所指的是 16MB 大内存页,不区分 “large page” 和 “huge page”,认为二者表示的内容相同,即都指的是 16MB 大内存页。

  可以在 Linux on POWER 上的虚拟的逻辑分区 (LPAR) 中使用 16MB 大内存页,也可以在将所有资源分配给该分区的全系统 LPAR 中使用 16MB 大内存页。

  应该设置专门用于 libhugetlbfs 的特定文件系统来只允许特定一组用户访问可用的大内存页池。我会在本文的后续部分提供相关示例。这对于系统安全性以及控制对系统上十分有限的宝贵资源(物理内存)的访问都十分重要。

  Linux 提供了可由根用户动态管理、指定、请求和返回 16MB 大内存页的能力。16MB 大内存页的可用性取决于被根用户请求的连续物理内存的系统可用性。16MB 大内存页可在引导时保留。

  当请求大量大内存页时,需要考虑内存的分段。系统运行时间越长,主内存越有可能被零碎固定和永久的内核内存分割,这就使得获得单个连续的 16MB 大内存页变得异常困难。如果需要的是大量的 16MB 大内存页,建议最好在系统引导时保留这些页。

  使用 libhugetlbfs 的几点限制

  系统大内存页是有限的宝贵系统资源。在考虑使用 libhugetlbfs 时,有如下几点限制:

  在 Linux 上,一个使用 libhugetlbfs 和大内存页的应用程序必须保证在运行时让所需要的所有大内存页都可用和空闲,否则若大内存页请求失败,应用程序就会在运行时终止。在如下这些情况下,这一点更需要格外注意:

  应用程序可能会使用很多内存,甚至多于可用的 16MB 大内存页。

  系统没有足够的物理内存来支持此执行体和所有特定页的加载。

  操作系统在用完大内存页时 “回落” 到常规页面的能力一直是 Linux 操作系统的一个难以很好实现的问题。在这一方面,社区还在不断的努力之中,目前也提出了多种原型和方案。随着系统硬件在所支持的页大小的数量上越来越灵活,相信操作系统中也会出现更多更灵活的控件。

  并非所有应用程序或软件解决方案都能从大内存页的使用中受益。利用大内存页最主要的性能改进是 Translation Lookaside Buffer (TLB) 遗漏的减少。通常建议您将透明大内存页尝试用于您的应用程序,看看是否会产生任何切实的性能改进。libhugetlbfs 所能带来的性能改进一般可以达到 10 % 到 15 %。但一些应用程序的性能也有可能会降低。一些应用程序的性能则会无改进或者有 少许改进,所以这种方式只适合在特定的应用程序为获得特定的性能改进而使用。

  总的来说,使用 libhugetlbfs 对于有限的一组应用程序来说都是一种提高性能的恰当方式。典型地,这种方法可用在需要极大型数据数组以及进行大量内存访问的 Fortran 程序,但这种方式也适用于任何 C、C++ 或 Fortran 程序。

  具有加载进大内存页的 .text 段(可执行文本)的应用程序往往不能有效剖析(例如,通过 oprofile)或调试(例如,通过 gdb),因为执行体本身会在没有征兆的情况下复制到匿名大内存页然后运行。在某些情况下,剖析十分有帮助,Linux 社区在这一方面还在不断努力以期扩展这一功能。

  libhugetlbfs 采用的是 Linux 内置的动态链接工具。所以,静态链接的执行体将不能利用 libhugetlbfs 的特性。

  执行体内的段

  开始之前,我们先来看看在所生成的执行体中包含的段。我会通过一个简单的 C 程序加以演示。在如下的示例中,我将声明一个大型的未初始化的数组和一个大型的已经初始化了的数组。

  清单 1 显示了称为 sections.c 的源文件。

  清单 1. sections.c 源文件#define ELEMENTS 1024*1024
static double bss_array[ELEMENTS];
static double data_array[ELEMENTS] = {5.0};
  
int  main()  {
  int  i; 
  for (i = 1; i < ELEMENTS; i++) {
    bss_array[i]  = data_array[i];
  }
} 

  bss 数组和 data 数组的主要区别很容易在下一步所生成的执行体中看出来。bss 和 data 数组的区别如下:

  bss 数组 —— 此数组是未初始化的数据,保存在执行体的 .bss 段。此段不占用执行体的任何空间。

  data 数组 —— 此数组是已初始化的数据(非零),必须保存在执行体本身的 .data 段。在上述情况下,每个双精度是 8 字节,所以要占用执行体中 8*1024*1024 (8MB) 字节的空间。

  要查看执行体中的这些特性,只需编译此程序,然后用 readelf 命令显示段信息,如 清单 2 所示。

  清单 2. C 程序中的段信息# cc sections.c -o sections
# ls -l sections
-rwxr-xr-x 1 root root 8400154 2006-12-03 18:52 sections
 
# readelf -S sections 
There are 36 section headers, starting at offset 0x8012cc:
Section Headers:
 [Nr] Name       Type      Addr   Off  Size  ES Flg Lk Inf Al
 [ 0]          NULL      00000000 000000 000000 00   0  0 0
 [ 1] .interp      PROGBITS    10000154 000154 00000d 00  A 0  0 1
 [ 2] .note.ABI-tag   NOTE      10000164 000164 000020 00  A 0  0 4
...
 [12] .text       PROGBITS    100002f0 0002f0 0005b0 00 AX 0  0 16
 [13] .fini       PROGBITS    100008a0 0008a0 000038 00 AX 0  0 4
...
 [23] .plt       PROGBITS    10010a28 000a28 000008 00 WA 0  0 4
 [24] .data       PROGBITS    10010a30 000a30 800008 00 WA 0  0 8
 [25] .bss       NOBITS     10810a38 800a38 800008 00 WA 0  0 8
 [26] .comment     PROGBITS    00000000 800a38 0000d9 00   0  0 1
...

  在 清单 2 所示的输出中,观察如下内容:

  执行体的大小 —— 执行体的大小是 8400154,对于较小的程序来说,这明显有些过大。

  .text —— 文本是编译器生成的可执行指令。对于大型程序而言,可引导 libhugetlbfs 将此段加载进 16MB 大内存页。在本例中,可执行文本的大小只有 5b0(hex) 字节,所以不应将其放至 16MB 大内存页。

  .data —— 程序中的数据段的大小为 800008(hex) 字节 。这与所声明的 8MB data 数组是相符的。初始化的数据是执行体本身的一部分,而这会使执行体变得很大,认识到这一点十分重要。通过偏移量则可以很容易看出这一点。数据段从偏移量 000a30 开始,而下一个段则开始于偏移量 800a38。

  .bss —— 程序中的 .bss 段的大小也为 800008(hex) 字节,但它并不占用执行体中的空间,.bss 段以及下一个段的开始偏移量不会更改,两个都是 800a38(hex)。

  将 data 数组更改为 bss 数组

  为了演示编译器是如何处理数组初值的,可以将 data_array 的初值从 5.0 更改为 0.0,注意到它现在变成了执行体中的 .bss 段,如下所示:

...
#define ELEMENTS 1024*1024
static double bss_array[ELEMENTS];
static double data_array[ELEMENTS] = {0.0}; <---- zero'ing makes this a .bss array
...

  如果将 data_array 初始化为零,编译器会将其视为 .bss 段。需要重新编译,结果如 清单 3 所示。请看如下的 .data 段。

  清单 3. 现在在 .bss 段中的 .data 段# cc sections.c -o sections
# ls -l sections
-rwxr-xr-x 1 root root 11546 2006-12-03 18:54 sections
# readelf -S sections
There are 36 section headers, starting at offset 0x12cc:
Section Headers:
 [Nr] Name     Type    Addr   Off  Size  ES Flg Lk Inf Al
 [ 0]        NULL    00000000 000000 000000 00   0  0 0
 [ 1] .interp    PROGBITS  10000154 000154 00000d 00  A 0  0 1
 [ 2] .note.ABI-tag NOTE    10000164 000164 000020 00  A 0  0 4
...
 [12] .text     PROGBITS  100002f0 0002f0 0005b0 00 AX 0  0 16
 [13] .fini     PROGBITS  100008a0 0008a0 000038 00 AX 0  0 4
... 
 [23] .plt     PROGBITS  10010a28 000a28 000008 00 WA 0  0 4
 [24] .data     PROGBITS  10010a30 000a30 000008 00 WA 0  0 4 
                    <----- No longer a data array
 [25] .bss     NOBITS   10010a38 000a38 1000008 00 WA 0  0 8
                    <----- Now twice the size
 [26] .comment   PROGBITS  00000000 000a38 0000d9 00   0  0 1
... 

  注意 size 列以及与大小相关的偏移量。所发生的更改描述如下:

  执行体的大小 —— 执行体的大小现在只有 11546 字节。

  .data —— 在 .data 段,大小从前例所示的 800008(hex) 降为只有 000008(hex)。data_array 现在被视为 .bss 段。

  .bss —— 在 .bss 段,大小现在是前例所示的两倍,即 1000008(hex),为两个数组大小之和( bss_array 的 800000(hex) 加上 data_array 的 800000(hex)),但它不会占用执行体中的任何 “空间”。下一个段的偏移量也不会更改。

  对段的总结

  为获得较大数量的内存,大多数应用程序会使用未初始化的数据声明或者使用 malloc 命令。 libhugetlbfs 提供了两种简单的方法来将 .bss 段和由 malloc 命令分配的内存加载进系统大内存页。libhugetlbfs 也提供了将执行体的 .text 段和 .data 段加载进大内存页的方法,但不如 .bss 段和由 malloc 分配的内存那么常见。

  设置 libhugetlbfs

  本节介绍下载、安装和设置 libhugetlbfs 的步骤。

  要用 libhugetlbfs 设置系统并定义系统大内存页,需要对此系统具有根访问权限。我还将介绍几种方法,一旦系统设置完毕,借助这些方法,非根用户也可以使用大内存页。

  要安装 libhugetlbfs,可以通过如下方式:

  如果您已经注册了 SUSE 支持,就可以从 Novell SUSE maintweb 支持站点访问最新的 libhugetlbfs rpms(注意:该站点只面向注册用户)。在本文编写之时,针对 SLES 10 所建议(和支持)的最新的 rpm 如下:

  libhugetlbfs-1.0.1-1.4.ppc.rpm

  libhugetlbfs-64bit-1.0.1-1.4.ppc.rpm

  在 SLES 10 最初刚刚可用的时候所提供的默认 libhugetlbfs 库已经过时,我建议最好不要使用它们,包括:

  libhugetlbfs-1.0-16.2.ppc.rpm

  libhugetlbfs-64bit-1.0-16.2.ppc.rpm

  安装了建议的库之后,可继续到本文的 分配大内存页 一节。

  在 RHEL 5,需要安装如下 5 个 rpm:

  libhugetlbfs-1.0.1-1.el5.ppc.rpm

  libhugetlbfs-devel-1.0.1-1.el5.ppc.rpm

  libhugetlbfs-devel-1.0.1-1.el5.ppc64.rpm

  libhugetlbfs-lib-1.0.1-1.el5.ppc.rpm

  libhugetlbfs-lib-1.0.1-1.el5.ppc64.rpm

  作为一种替代方案,您也可以从 SourceForge(参见 参考资料)获得副本并构建和安装此版本。我接下来就会介绍这种方法。

  从 SourceForge 安装

  从 SourceForge,单击 Download libhugetlbfs 按钮,如 图 1 所示,并遵循如下操作指导来下载 tar ball。对于本文而言,Version 1.0.1 级是经测试的且可由 SUSE 和 Red Hat 支持。

  图 1. SourceForge libhugetlbfs 页

  在 Linux on POWER 上利用透明大内存页

  将 tar ball 下载到系统。libhugetlbfs make install 过程会将所需的文件复制到系统中合适的位置。我建议通过指定 make install PREFIX=/usr 在系统的 /usr 子目录安装 libhugetlbfs,如 清单 4 所示。您需要根访问权限才能安装。

  清单 4. 安装 libhugetlbfs# tar -zxf libhugetlbfs-1.0.1.tar.gz
# cd libhugetlbfs-1.0.1
# make
   ...        <---- not showing "make" messages here ...
# make install PREFIX=/usr
   VERSION
   INSTALL32 /usr/lib
   INSTALL64 /usr/lib64
   OBJSCRIPT ld.hugetlbfs
   INSTALL

  如果察看 /usr/share/libhugetlbfs/ 目录,您会发现 ld,它是新的链接器命令。参看 清单 5。ld 命令被软链接到 ld.hugetlbfs 以方便 GNU Compiler Collection (GCC) 和 IBM 编译器调用此链接器。

  清单 5. /usr/share/libhugetlbfs/ 显示 ld 命令# ls -l /usr/share/libhugetlbfs
total 8
lrwxrwxrwx 1 root root  12 2006-11-26 12:42 ld -> ld.hugetlbfs
-rwxr-xr-x 1 root root 1321 2006-11-26 12:42 ld.hugetlbfs
drwxr-xr-x 2 root root 4096 2006-11-26 12:42 ldscripts

  清单 6 中所示的 ldscripts 子目录包含所有修改后的链接器脚本,这些脚本是处理重链接 .bss、.data 和 .text 段所必需的。

  清单 6. /usr/share/libhugetlbfs/ 的 ldscripts 子目录/usr/share/libhugetlbfs/ldscripts # ls
elf32ppclinux.xB  elf64ppc.xB  elf_i386.xB  elf_x86_64.xB
elf32ppclinux.xBDT elf64ppc.xBDT elf_i386.xBDT elf_x86_64.xBDT

  libhugetlbfs 包附带了很多自动测试包,这些测试包可通过 make 调用。这些测试主要由 libhugetlbfs 包开发人员及内核维护人员使用,这是因为更改是对系统做出的。如果您运行的是受支持的操作系统级和内核,则无需运行这些测试,对于该包的一般用户来说更应如此,因为测试的输出对于这些用户来说过于晦涩难懂。

  libhugetlbfs 提供的另一个重要文件是 libhugetlbfs.so 库,如 清单 7 所示。该库在运行时调用,可控制大内存页的系统使用。这类库分别针对 32 位应用程序和 64 位应用程序提供。

  清单 7. libhugetlbfs.so 库# ls -l /usr/lib/libhugetlbfs.so
-rwxr-xr-x 1 root root 54785 2006-11-26 12:42 /usr/lib/libhugetlbfs.so
 
# ls -l /usr/lib64/libhugetlbfs.so
-rwxr-xr-x 1 root root 63910 2006-11-26 12:42 /usr/lib64/libhugetlbfs.so

  分配大内存页

  要设置系统使之使用大内存页,需要首先分配大内存页。对于 Linux,就是复制(回显)想要分配进 /proc/sys/vm 控件的大内存页数量的值,如 清单 8 所示。该操作系统试图分配所请求的大内存页。分配 意指将页隐藏起来并将它们保留在物理内存池外。这些页尚未使用。系统也未给出任何指示,说明它未能分配所有请求的大内存页。所以您请求完这些页后,总是需要检查实际保留了多少页。

  清单 8. 用 /proc/sys/vm 分配大内存页# cat /proc/meminfo | grep Huge
HugePages_Total:   0
HugePages_Free:   0
HugePages_Rsvd:   0
Hugepagesize:  16384 kB
  
# echo 200 > /proc/sys/vm/nr_hugepages
   
# cat /proc/meminfo | grep Huge
HugePages_Total:  200
HugePages_Free:  200
HugePages_Rsvd:   0
Hugepagesize:  16384 kB

  定义 hugetlbfs 文件系统

  libhugetlbfs 使用虚拟的文件系统接口,hugetlbfs。要设置该接口,需要创建挂载点并挂载虚拟文件系统。文件系统可以命名为任何惟一的名称,在本例中,使用挂载点 libhugetlbfs。

  您所定义的 hugetlbfs 可以控制谁有对系统大内存页的访问权,所以很有必要设置一个对系统大内存页有访问权的用户组。注意,本例在系统上创建一个特殊的组(libhuge)来控制对 hugetlbfs 的访问,参见 清单 9。您应该将授权用户添加到该组,只有这些用户可以访问由 libhugetlbfs 使用的大内存页。

  清单 9. 定义 hugetlbfs 文件系统# mkdir /libhugetlbfs
 
# groupadd libhuge
# chgrp libhuge /libhugetlbfs
# chmod 770 /libhugetlbfs
# usermod wmb -G libhuge  <---- assumes "wmb" is a user on the system
# mount -t hugetlbfs hugetlbfs /libhugetlbfs

  Linux 操作系统使用这个特殊的文件系统来控制到与 16MB 大内存页相关的物理内存的访问和控制。您所创建的文件系统的名称是什么并不重要,对于一般的使用来说,您应当定义和使用单一的文件系统。请记住 hugetlbfs 的 “挂载” 只是暂时的,而且它不会在引导时被自动重新挂载,除非您更改了 /etc/fstab。要使此更改永久有效,需要编辑此文件并添加如下所示的可应用行:# vi /etc/fstab

  然后将该行添加到此文件,如 清单 10 所示。注意 gid=1000 假设 libhuge 组 ID 是 1000。

  清单 10. 将 libhugetlbfs 挂载添加到 /etc/fstab/dev/sda3      /          ext3    acl,user_xattr    1 1
/dev/sda2      swap         swap    defaults       0 0
proc         /proc        proc    defaults       0 0
sysfs        /sys         sysfs   noauto        0 0
debugfs       /sys/kernel/debug  debugfs  noauto        0 0
devpts        /dev/pts       devpts   mode=0620,gid=5    0 0
/dev/fd0       /media/floppy    auto    noauto,user,sync   0 0
hugetlbfs      /libhugetlbfs    hugetlbfs mode=0770,gid=1000  0 0

  libhugetlbfs 准备就绪

  至此,就完成了 libhugetlbfs 库的基本设置,包括:

  了解了对使用 libhugetlbfs 的一些考虑

  下载并安装了 libhugetlbfs(方法可以是从 SourceForge 安装,也可以利用 SLES 10 或 RHEL 5 发行版的最新 rpm 文件)

  通过创建、挂载和限制对虚拟 hugetlbfs 的访问设置 libhugetlbfs

  定义了可由用户使用的一个大内存页池

  现在,用户就可以使用透明大内存页了。

  用大内存页备份 malloc 命令

  首先来看看 malloc 命令,因为它简单且无需特殊链接。假设您当前以用户身份运行(在本例中,为 wmb),该用户包含在之前定义好的 libhuge 组中。挂载后的 libhugetlbfs 文件系统将 libhuge 作为该组的所有者,wmb 属于该组,如 清单 11 所示。

  清单 11. libhuge 组wmb@p5sys:~> ls -l -d /libhugetlbfs/
drwxrwx--- 2 root libhuge 0 2006-12-18 21:26 /libhugetlbfs/
wmb@p5sys:~> id
uid=1000(wmb) gid=100(users) groups=16(dialout),33(video),100(users),1000(libhuge)

  然后使用一个简单的程序来从一个大型的数组复制值到另一个数组。所创建的程序名为 copy_arrays.c,如 清单 12 所示。

  清单 12. copy_arrays.c#define ELEMENTS 1024*1024*128
static double bss_array_from[ELEMENTS];
static double bss_array_to[ELEMENTS];
static double *malloc_array_from;
static double *malloc_array_to;
int  main()  {
  int  i;
  malloc_array_from = (double *)malloc(ELEMENTS*sizeof(double));
  malloc_array_to  = (double *)malloc(ELEMENTS*sizeof(double));
  /* initialize and touch all of the pages */
  for (i = 1; i < ELEMENTS; i++) {
    bss_array_to[i]   = 1.0;
    bss_array_from[i]  = 2.0;
    malloc_array_to[i]  = 3.0;
    malloc_array_from[i] = 4.0;
  }
  /* copy "from" "to" */
  for (i = 1; i < ELEMENTS; i++) {
    bss_array_to[i]  = bss_array_from[i];
    malloc_array_to[i] = malloc_array_from[i];
  }
  return;
}

  以根用户身份,分配 200 个大内存页给系统使用。然后打开第二个窗口并发出 watch 命令来跟踪大内存页的系统使用情况,如下所示:

# echo 200 > /proc/sys/vm/nr_hugepages
# watch cat /proc/meminfo

  以非根用户的身份(在本例中,即 wmb),编译和运行该程序,如 清单 13 所示。被 “观察” 的大内存页的数量不应改变。但 “次数” 可能会因运行的系统的不同而有所不同。

  清单 13. 运行 copy_arrays.cwmb@p5sys:~> cc -O3 -m64 copy_arrays.c -o copy_arrays
wmb@p5sys:~> time ./copy_arrays
real  0m6.844s
user  0m2.149s
sys   0m2.649s

  要用大内存页备份 malloc 命令,需要设置 HUGETLB_MORECORE 环境变量,并将 LD_PRELOAD 设为 libhugetlbfs 库,如 清单 14 所示。程序应该执行得稍微快一些,您用来观察内存使用情况的窗口应该显示被分配和使用的大内存页。

  清单 14. 设置 HUGETLB_MORECORE,其中 LD_PRELOAD 设为 libhugetlbfswmb@p5sys:~> time HUGETLB_MORECORE=yes LD_PRELOAD=libhugetlbfs.so ./copy_arrays
real  0m6.480s
user  0m2.483s
sys   0m1.645s

  根据您的系统和内存配置的不同,结果可能会有些差异,但在本例中,性能应该有 5% 左右(或 (6.480-6.844)/6.844)的提高。虽然这看起来是非常微小的一点时间,但若运行的是历时数天的计算密集型的工作负载,则性能改善的效果就会叠加。

  LD_PRELOAD 设置会使 malloc 命令或其派生命令自动地由之前分配好的大内存页进行备份。 Malloc 命令在运行时处理,所以您将需要预定义足够多的 16MB 页供程序使用。

  用大内存页备份段

  当重链接执行体来利用 .bss、.data 或 .text 段备份时,很容易使用 GCC 或 IBM 编译器。这两个编译器都提供了可用来重写 ld 命令调用的易用参数。它们的使用方法如下所示:

  GCC 编译器:将如下参数添加到 C 编译链接命令行:-B /usr/share/libhugetlbfs/ -Wl,--hugetlbfs-link=B

  第二个参数是 'dash W ell comma' —— 这在很多浏览器上都不易读懂。

  IBM 编译器:使用方法相同,但提供了额外的对被重写内容的解释说明,比如:-B /usr/share/libhugetlbfs/ -tl -Wl,--hugetlbfs-link=BDT

  这会添加 'dash tee ell',它会将此指令定向到链接器。可将此方法用于 C、C++ 和 Fortran 编译器。

  因此,使用 copy_arrays.c 程序(清单 12)和 GCC 编译器,就可以告诉链接器将 .bss 段放入大内存页。注意,在本例中,需要创建新的目标执行体,名为 copy_arrays-lp,如 清单 15 所示,原因是该执行体是针对大内存页链接的。您应该会看到与备份 malloc 页类似的性能改进。

  清单 15. copy_arrays-lp 执行体wmb@p5sys:~> cc -O3 -m64 -B /usr/share/libhugetlbfs/ -Wl,--hugetlbfs-link=B
          copy_arrays.c -o copy_arrays-lp
         
wmb@p5sys:~> time ./copy_arrays-lp
real  0m6.495s
user  0m2.558s
sys   0m1.648s

  若 16MB 页不够又该如何呢?

  您应该将二者结合,即用大内存页备份 .bss 段 和 malloc。您将需要比预先定义更多的大内存页。

  注意,当没有定义足够的大内存页时,应用程序会被终止(“杀死”)。如 清单 16 所示,如果没有足够的 16MB 大内存页可用,使用了 malloc 的 copy_arrays_lp 调用将会失败。通过分配更多的 16MB 大内存页然后重运行可以解决这个问题。

  当使用针对 .bss 段链接的执行体时,无需指定 LD_PRELOAD=libhugetlbfs.so 重写。您只需通过定义 HUGETLB_MORECORE=yes 来指定使用 malloc 即可。

  清单 16. 通过 HUGETLB_MORECORE 指定使用 mallocwmb@p5sys:~> time HUGETLB_MORECORE=yes ./copy_arrays-lp
Killed                   <---- Note the "Killed" message
real  0m4.084s
user  0m1.669s
sys   0m0.365s
wmb@p5sys:~> su
#> echo 300 > /proc/sys/vm/nr_hugepages
#> exit
wmb@p5sys:~> time HUGETLB_MORECORE=yes ./copy_arrays-lp
real  0m6.943s
user  0m3.600s
sys   0m0.473s

  在本例中,使用 malloc 和 .bss 段并不会获得明显的性能改进。对应用程序尝试各种不同的组合有助于您理解哪种方式最为实际和有效。并不是所有应用程序都能从透明大内存页的使用中受益。

  未授权的用户

  若未授权的用户尝试使用系统大内存页时,虽然程序仍会正常运行,但 libhugetlbfs 库却会返回错误 —— 大内存页将不能使用。例如,若用户 joe 试图运行上述相同的两个程序,就会得到如 清单 17 所示的结果。

  清单 17. 未授权的用户试图使用大内存页的结果joe@p5sys:~> time HUGETLB_MORECORE=yes ./copy_arrays-lp
libhugetlbfs: ERROR: mkstemp() failed: Permission denied
real  0m6.795s
user  0m2.267s
sys   0m2.656s
joe@p5sys:~> time HUGETLB_MORECORE=yes LD_PRELOAD=libhugetlbfs.so ./copy_arrays
libhugetlbfs: ERROR: mkstemp() failed: Permission denied
libhugetlbfs: ERROR: Couldn't open hugetlbfs file for morecore
real  0m6.807s
user  0m2.133s
sys   0m2.660s

  libhugetlbfs 的应用示例

  libhugetlbfs 库已被用在很多客户生产环境中,帮助跨 POWER 调优工作负载,并可用于带 SLES 10 的 SPECcpu2000 发布。最近,它还成功地用在了具有 RHEL 5 新版本的 SPECompM2001 发布中。此外,在 IBM Linux on POWER wiki(参见 参考资料)上还提供了如何调优内存应用程序的实际应用示例。

  用 SLES 10 发布 SPECcpu2000

  为了提供该技术的实际应用示例,IBM 最近在 SPEC.org Web 站点(参见 参考资料)上发布了一系列有关 POWER5+ 系统上使用 SLES 10 的 SPECcpu2000 基准。所发布的这些示例充分展示了调用和使用 libhugetlbfs 可以非常容易。比如,在 SPECint2000 运行上,执行体可正常构建。之后,当这些运行就性能进行评测时,通过设置两个可应用的环境变量就可以开启这些执行体的 malloc 备份,如 清单 18 所示。

  清单 18. 在 16MB 大内存页中用 malloc 命令运行 SPECint2000# export HUGETLB_MORECORE=yes
# export LD_PRELOAD=libhugetlbfs.so
# runspec --config LoP_config_file.cfg int

  对于 SPECcpu2000 Fortran 发布,需要利用常见的重链接可应用程序的方式设置执行体以便用 16MB 页备份 .bss、.data 和 .text 段。在本例中,就是修改组件的链接步骤。在 清单 19 所示的示例中,显示了针对 179.art 组件所做的更改,正如在 SPEC.org Web 站点所公布的那样(参见 参考资料)。

  清单 19. SPECfp2000 组件编译/链接标志的示例 179.art=peak=default=default:
 notes179_1    = 179.art
 notes179_2    =   +FDO -O5
 notes179_3    =   -B/usr/share/libhugetlbfs/ -tl -Wl,--hugetlbfs-link=BDT
 
 PASS1_CFLAGS   = -qpdf1 -O5 -B/usr/share/libhugetlbfs/ -tl -Wl,--hugetlbfs-link=BDT
 PASS1_LDCFLAGS  = -qpdf1 -O5 -B/usr/share/libhugetlbfs/ -tl -Wl,--hugetlbfs-link=BDT
 PASS2_CFLAGS   = -qpdf2 -O5 -B/usr/share/libhugetlbfs/ -tl -Wl,--hugetlbfs-link=BDT
 PASS2_LDCFLAGS  = -qpdf2 -O5 -B/usr/share/libhugetlbfs/ -tl -Wl,--hugetlbfs-link=BDT

  然后调用此运行,如下所示:

# export HUGETLB_MORECORE=yes
# runspec --config LoP_config_file.cfg fp

  用 RHEL 5 发布 SPECompM2001

  利用 RHEL 5 发布最新的 SPECompM2001 可以在 IBM System p5 560Q 系统完成。结果可以在 SPEC.org Web 站点找到(参见 参考资料)。

  通过使用 16MB 大内存页备份的 malloc 命令而展现出性能改进的特定组件可以使用正确设置的环境变量来运行。清单 20 展示了其中一个组件的调用是如何被修改的。这看起来很容易,不是么?

  清单 20. SPECompM2001 组件在 16MB 页中使用 malloc 的例子316.applu_m: -qpdf1/pdf2
       -O4 -q64
       ENV_HUGETLB_MORECORE=yes
       ENV_LD_PRELOAD=libhugetlbfs.so

  应用程序调优的例子

  利用 libhugetlbfs 调优内存应用程序(流)的另一个练习可以在 IBM Linux on POWER wiki 上找到(参见 参考资料)。

  wiki 中的例子显示了用户可以采用哪些常见的实际步骤来最大化运行 Linux 的系统的内存密集型工作负载的性能。

  结束语

  本文介绍了一个开源社区项目,称为 libhugetlbfs,它现在受 SLES 10 和 RHEL 5 支持。此库为客户应用程序提供了一种更简单的透明访问系统大内存页的方式。在本文中,您了解了如何结合使用 libhugetlbfs 库与 GCC 或针对 Linux 的 IBM XL C/C++ 编译器。libhugetlbfs 库让您可以将大内存页用在程序中的 malloc 命令、.bss 段或执行体中 .bss 段、.data 段和 .text 段的组合中,在某些情况下,这样做可以改善程序性能。您可以很容易地在系统上配置 libhugetlbfs,而且还可以很容易将访问控件包括进来以限制对内存资源的访问。

  致谢

  我要特别感谢 Adam Litke(居于 Rochester,MN)、Nishanth Aravamudan(居于 Beavertoox,OR)、Steven Fox(居于 Rochester,MN)和 David Gibson(居于澳大利亚),感谢他们的不懈努力,是他们在 2006 年将 libhugetlbfs 作为一种受支持库带给了社区。也感谢他们在 2007 所做的贡献,他们使此库具有了更高的跨系统平台的灵活性和健壮性。

  我还要感谢 developerWorks 的长期供稿人 Chakarat Skawratananond(居于 Austin,TX),感谢他在将 libhugetlbfs 用于为客户改善性能方面所提供的帮助和协助。

  

描述名字大小下载方法
SourceForge libhugetlbfs 库libhugetlbfs-1.0.1.tar.gz64KBHTTP
XL C/C++ Advanced Edition V8.0 的测试版1vac.rpm95MBHTTP

来源:ibm    作者:Bill Buros    责编:豆豆技术应用

正在加载评论...