确定系统的可用物理内存并用它来改进应用程序

作者:Greg Nakhimovsky

摘要

本文探究了应用程序如何可以发现可用空闲内存数量以及如何利用这一信息。针对 Solaris Operating System (Solaris OS) 本文还提供了确定当前可用内存的方法,并提供了简单的示例来说明这一接口的使用方法。

简介

计算机编程的基本概念之一是空间/时间的折衷问题。一般的观念是使用更多的内存可以提高程序的性能。另外可供选择的方法是如果您愿意为它牺牲性能可以使用更少的内存。在本文,我主要集中在前一观点,那就是研究如何依靠一定数量的可用物理内存改进应用程序的性能。

应用程序可以通过许多方法用速度来交换可用物理内存。下面是一些示例:
  • 表查找。在许多情况中,您可以预先计算许多数值,把它们保存在内存中,然后查找结果而非进行计算。如果整个表都可以装进物理内存,访问表是非常快的操作。举一个使用表查找的简单示例,参见下面的示例:计算人口计数。同样,可以参见“表查找” (参考 [7]).

  • 缓存,它可以通过不同的方式使用,比如通过 web 浏览器和网络文件系统。您可以把结果存储在内存里并在稍后再度需要使用的时候使用,而不用每次都计算结果或从远程地点传递数据。

  • 散列法,它提供一种通过使用额外内存来搜索大量未分类数据集的快速方法。参见“Hashing in Forth” (参考 [8])。

  • 使用 OpenGL 图像的显示列表。OpenGL 显示列表是存储在本地的 OpenGL 命令特定最优缓存。您可以一次性定义,然后多次使用,这样可以提高图像应用程序的性能。
在应用程序使用任何空间/时间折衷方案之前,您需要估计在不引起磁盘页面调度的情况下程序里有多少空闲的物理内存。

这种估计和后面使用这一信息可能有所不同,这有许多原因:
  • 不同的系统在同一时刻提供不同的方法(如果有的话)来确定可用空闲物理内存数量。所以获得这一信息的方法是高度平台依赖性的。不同系统的信息精确性也有或多或少的变化。

  • 取得的空闲内存数量服从不同的竞态条件,特别是在多用户多进程的操作系统中(在今天它们是主体)。您得到的空闲内存数量有的时候可能是无效的,这是因为另一个程序随时可能消耗额外的内存并改变了空闲内存的数量。如果您的应用程序是多线程的,那么在您自己的应用程序中的其它线程也可能消耗更多的内存,进而使这一信息无效。

  • 应用程序可能知道也可能不知道将来它所需要的内存数量。比如交互式应用程序未来内存需求可能取决于用户的行为(到现在为止还不能确定)。
因为有这些困难,大多数应用程序开发者都不使用任何直接的空间/时间折衷方案来改进应用程序。他们只是简单地假设系统为所选的算法提供了足够的内存。当物理内存不足时,大部分现代的操作系统将使用虚拟内存的功能,这一功能是使用磁盘交换空间来补充物理内存。

虚拟内存系统的主要问题是当开始向磁盘进行页面调度时应用程序的性能会受到很大影响。典型磁盘访问的速度比任何类型的物理内存访问速度都要慢许多数量级。因此,您需要尽量避免对磁盘进行页面调度。在大部分情况下,理论上没有性能优化值得引进磁盘页面调度。

所以目前面临的挑战是要找出确定空闲物理内存的方法,并且有成果地使用它而不引起磁盘页面调度。

Solaris OS 中如何确定可用物理内存

Solaris OS 有 kstat(3KSTAT) 工具,它允许任何程序访问各种 kernel 统计值。kstatlibkstat.so 为系统的 kstat 工具定义了应用程序用户接口(API)。

在 Solaris OS(特别是从 Solaris 8 开始)中,当前可利用的空闲物理内存数量可以从 freemelotsfree 值得出。除非 freemem 下降到 lotsfree 以下,否则磁盘页面调度不会发生。因此,接下来的数值表示在不引起磁盘页面调度(也就是在不引起页面扫描的前提下)的情况下应用程序可以安全使用的内存数量:
freemem - lotsfree

Solaris 8 引进了一种新的文件系统缓存体系结构,循环页面缓存。所以 freemem 会更精确地报告空间物理内存的数量。(在 Solaris 8 之前,因为从中减去了文件缓存内存,所以 freemem 报告的数量通常很低。)

随时可以使用 phys_mem_avail() 程序以千字节获得空间物理内存的数量。它基于 kstat(3KSTAT) 接口。

phys_mem_avail.c

正如您所看到的,这种方法获得了 kernel 的值 freemem lotsfree 并以千字节返回它们之间的差值。

要注意返回的值可能是负数,这意味着即将进行或已经在进行磁盘页面调度。负值的绝对值越大,页面扫描的比率就越高,而页面调度发生的也就越多。

在这种方法里,freemem 的值和 vmstat(1) 实用程序在它输出行第一行之后报告的 free memory 值一样。lotsfree 值默认为系统中总物理 RAM 的 1/64(以满足内部 kernel 需要的少量内存数量递减)。默认的 lotsfree 值可能在 /etc/system 中被重写,所以在 kernel 中获得它真实值是个好办法。

phys_mem_avail() 里使用的大部分系统参数只确定一次,也就是它们第一次被调用的时候。每次都会更新的值只有 freemem。

您也可以确定除了当前可用空闲物理内存以外的更多信息。在 Solaris OS 中,当 freemem 下降到 lotsfree 以下直到到达 minfree,磁盘页面调度的强度都是线性增加的。因此,您可以通过这样的代码判断最大磁盘页面调度的百分率:
   static size_t minfree;

   float percent_paging;



   if(pagesize == 0)

   {

..

     if((kn=kstat_data_lookup(sys_pagesp, "minfree")) == 0)

       perror("kstat_data_lookup(minfree)"),exit(1);

     minfree = kn->value.ul;

   }

..



   if(freemem > lotsfree)

     percent_paging = 0.0;

   else

     percent_paging = (float)(lotsfree - freemem) /

       (float)(lotsfree - minfree)*100.0;

关于 kstat(3KSTAT) 工具的更多信息请参见 Sun Product Documentation([1])、Solaris Internals ([2])、在线文章 "Solaris Kernel Statistics," 第一和第二部分 ([4])、文章 "The Solaris 8 Memory Architecture: Better, Simpler, and Faster" ([3]) ,普林斯顿大学的 Unix 系统小组文档  ([5]) 也提供了关于这一主题的有用信息。

在 Linux 操作系统中,可以用 sysinfo(2) 系统调用确定类似的信息。在结果结构中的 freeram 值将包含当前可利用物理内存的字节数量。
 

在您的程序里检测磁盘页面调度

在 Solaris OS 操作系统中,您可以使用至少两种方法来确定系统此刻是否在进行磁盘页面调度或将要关闭磁盘页面调度。

  1. 确定可利用空闲物理内存的数量(如上述方法)并查看它是不是负数,负数则表示系统是正在进行页面调度或将要开始页面调度。

  2. 对特定的进程(您自己的进程)使用微观状态会计学查出进程等待内存中数据所花费的时间百分比(prstat -m 的 DFL 栏显示)。非零值意味着系统正在进行磁盘页面调度。
您可以使用这两种方法来互相检验。在本文,我使用第一种方法。

一旦知道了系统在进行磁盘页面调度,您的应用程序可以做的一件有用的事就是向用户发出警告。指出系统正在进行页面调度,而这可能会引起明显的性能下降。同样也提供数字的提示,比如空闲内存下降到 lotsfree 极限以下的千字节数量。

应用程序应该每隔一段时间就检查一下这一情况(比如说每隔一分钟)。您可以在一个特定的线程里做这件事。您可以在调用 phys_mem_avail() 期间调用 sleep(3C)nanosleep(3C) 来延缓当前线程的执行。

列出用户可以消除磁盘页面调度步骤也很有用。在 Solaris OS 中,用户甚至可以在不退出应用程序的情况下尝试下面的步骤:
  • 关闭其他可能消耗大量内存的应用程序。
  • /tmpswapfs 使用内存)删除不必要的文件。
当然,增加 RAM 是另一种可能的解决方案,但那需要先退出应用程序。

您可以把这种警告输出到特定的日志文件或在单独的窗口显示出来(如果您的应用程序使用图形用户接口)。

一次性改进应用程序的更好方法是当您发现在进行磁盘页面调度时,动态调节程序的逻辑以适应低内存的情况。比如除了警告用户,您也可以在这时转换到速度慢但内存消耗低的算法。这使应用程序变得更加自适应,因而也更有智能。
 

通过使用可用内存提高应用程序性能

这种情况比简单地警告用户正在进行磁盘页面调度更困难并且更不可靠,但在许多情况下它都是可能的。

通常,应用程序没有足够的信息对内存的使用做出智能的决策。但还有办法来获得失去的信息,至少在某些情况下可以:
  • 您做为一个应用程序开发者,可能只简单的知道用户的情况。比如您的应用程序可能要求或采用专门的机器,不允许其他用户或应用程序使用系统,至少在您的应用程序运行时不允许消耗机器上的任何重要资源。
  • 您可能也知道在某种程度上应用程序不太可能请求比它已经消耗的内存更多的内存。
一旦有了这一额外的知识,您就可以使用它来确定哪一种算法在这种情况下会提供更好的性能。如果调用 phys_mem_avail() 告诉您此刻有足够的物理内存,并且如果您可以确定为了达到您的目的依靠这些空闲内存足够,那么您可以动态地选择更快的算法来通过这些内存来性能性能。

如果您不知道用户的情况并且不能进行安全地假设,您可以询问用户并给他选择算法的机会:选择更快且消耗更多内存的算法,还是选择速度慢但消耗内存为了性能通过的算法。

您可以采用不同的方法询问用户。在交互式的应用程序里,您可以提供一个对话框。在其他情况,一些配置文件可能更适合。

这种情况类似于汽车里手动和自动变速器的选择。这里也是,自动变速在许多情况下没有为确定最优方案提供足够的信息。(比如在下一分钟里您将要上坡还是下坡?您要加速还是减速?)手动变速允许用户做出决策,但这使自动化程度和自适应程度降低。注意一些“强大”的用户在汽车变速器和计算机应用程序中都更喜欢自己控制。因此为应用程序用户提供这种选择是很好的想法。
 

示例:计算人口计数

在文章“Once Upon a Time-Memory Tradeoff” (参考 [6])中, Mark Stamp 举了一个简单的关于空间/时间折衷的示例:计算人口计数。(他也描述了许多更复杂的示例,但在这里我将只使用最简单的示例。)类似这种算法在密码学和其他领域可以找到。

人口计数 popcnt(x) 有一个非负的整数 x 被定义为以二进制表示的 x 的数量。比如 popcnt(13) 是 3,因为 13 的二进制形式是 1101。

注意,这一简单的程序用任何算法都不会消耗大量内存。同样,也有更聪明更快速的计算非负整数位数的算法。因此这一示例并不很实用。我只是用它说明为性能交换可用内存的效果以及在应用程序里它是如何完成的。

下面是示例程序:

popcnt.c

任务是计算人口计数的以 0 开始的前 100,000,000 个数。

程序必需的计算一共有三次:第一次是使用明显的强力方法,然后提前计算出前 256 个值,然后再预先计算出前 65536 个值。有关算法的更多细节,为什么预先计算出许多值以及在这种情况下使用表查询可以提高性能,请参见“Once Upon a Time-Memory Tradeoff” (参考 [6])。

在 Solaris 9 OS 下的 360-MHz Ultra 10 工作站编译运行这一示例程序函数。注意为了演示示例程序,我没有使用任何编译器优化,只是让问题更清晰。当然在真实的应用程序使用编译器优化是很好的方法
% cc -c phys_mem_avail.c

% cc popcnt.c phys_mem_avail.o -lkstat

% a.out

SUM(Obvious_popcnt) = 1314447104

 Time:    33.02 sec

-----------------

Available physical memory: 246456 Kbytes

Memory required for storing 256 values:  1 Kbytes

Time to precompute 256 values:     0.00 sec

SUM(using 256 precomputed values) = 1314447104

 Time:     6.25 sec

-----------------

Available physical memory: 246456 Kbytes

Memory required for storing 65536 values:  256 Kbytes

Time to precompute 65536 values:     0.03 sec

SUM(using 65536 precomputed values) = 1314447104

 Time:     3.45 sec

% 

正如您所看到的,预先计算并保存一些数量在内存里,然后使用表查找,已改进了这一程序的的性能:
33.02/3.45 = 9.57 times

您也可以从这些结果里看到如何使用更多的内存来保存已经获得更高性能的预先计算数据。

调用 phys_mem_avail(),确保在使用更快的算法之前有足够的可用物理内存可以使这一程序更智能和健壮,并且性能也可以获得更多保证。

感谢

为了 Iain Bason(他是我的一位 Sun 同事)的在 Solaris OS 中使用 kstat(3KSTAT) 来确定当前可用物理内存的想法,我要感谢他。

关于作者

Greg Nakhimovsky 是 Sun Microsystems 的工程师,他与应用软件开发商合作以确保他们的产品在 Sum 系统上运行正常。他有超过20年的开发、性能调整和在技术上解决计算机应用的行业经验。

参考资料

 

常见问答

下载中心

产品简介

 

 

Solaris论坛