运行时:Linux 和 Windows 2000上的高性能编程技术

2024-04-23 12:32

本文主要是介绍运行时:Linux 和 Windows 2000上的高性能编程技术,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

运行时:Linux 和 Windows 2000上的高性能编程技术

建立计时例程

      developerWorks

级别: 初级

Edward G. Bradford, 高级程序员, IBM

2001 年 4 月 01 日

欢迎光临本专栏,这个新的 Linux 专栏主要演示和比较了 Linux 和 Windows 2000 操作系统的性能。专栏作家 Ed Bradford 比较了操作系统级的特性,而不是应用程序,以便让人们了解每个操作系统的最佳性能特性。本文包含了源代码,在尽可能公平的环境中,它们表示每个平台的“最佳编程实例”。

在这个新的文章系列中,将主要讨论用于 Linux 和 Windows 2000 操作系统的高性能编程技术。我将演示实用且有效的编程实例,它们能够解决 Linux 和 Windows 2000 上出现的同一问题。问题解决之后,就至少可以对每个平台进行某一方面的性能测量。各个性能测试脚本和程序将会显示操作系统特性的速度。我的目标是演示如何得到每个操作系统可能的最佳性能,顺便比较一下这两个操作平台的性能。

性能测试概述

这些测试将检测内存速度、系统调用速度、输入输出总和、上下文切换速度和许多其它在这两种操作平台上通用的编程工具。但是,不会测量对 Windows 注册表的访问。本文中发表了源代码,也可以免费下载这些源代码。在这里追求的是富有建设性的评价。我的目的是首先展示最好的编程实例 -- 然后比较性能。十分欢迎读者在 讨论论坛上针对本文发表您的看法。

我们将 Linux 看作是两个操作系统:Linux 2.2.16 内核和 2.4.2 内核。Windows 2000 是指版本 Windows 2000 2195 系统,发行时,称作 Windows XP 内核。所有测试都基于完全相同的硬件。首选硬件是双引导 IBM ThinkPad 600X(带 576 MB 内存和两个 12-GB 磁盘)。

虽然很少用到面向对象的代码,但还是用 C++ 编写了程序。使用 C++ 的原因只是 C 具有很强的类型检查特点。在 Linux 上,使用 Red Hat 7.0 分发版所带的 gcc。在 Windows 2000 上,使用 Visual Studio 6.0 下的 Microsoft C++ 版本 12.00.8168。





测量实用程序

首篇文章定义了对 Windows 2000 和 Linux 进行测量和报告测量结果所需的实用例行程序。所列出的工具很少:用于测量时间的接口、返回描述操作系统的字符串的例程、简单 malloc() 内存分配例程的简单且有效的接口,以及处理大数的输入例程。(以下将详细讨论定时例程)。

这里的内存分配器称为 Malloc(int)。它所做的就是调用 malloc(int) 例程,如果 malloc(int) 失败,Malloc() 打印一条错误消息并退出。在测试内存分配性能时,没有用这个例程,但同时,它是流线形编码。这里使用 malloc() 是由于在 Windows 2000 和 Linux 中都有这个函数。Malloc() 在这两个系统中是公平而等同的。

接下来的一个例程是 atoik(char *)。这个例程与 atoi() 例程是相同的,但它带一个后缀 "k" 或 "m"。后缀 "k" 或 "m" 表示:对于 "k",将已解析的数字乘以 1024,对于 "m",将已解析的数字乘以 1024*1024。"k" 和 "m" 可以是大小写,并且可以给它们附加任何数字。Atoik() 只返回 32 位,所以当出现任何大于或等于 2 GB 的数字时将不能继续运行。当出现这个问题时,使用我们编写的 atoik64() 函数。这个例程在两个操作系统中是相同的。





测量时间

在这两个操作系统上如何测量时间?让我们看一下我们有几种选择。在 Windows 2000 中有两个 API 可以测量时间间隔。第一个是 GetTickCount()。这个函数报告自从系统启动后经过的毫秒数。GetTickCount() 是以“时钟报时信号”为颗粒度。这意味着只有当系统发出时钟报时信号时这个函数才会更新这个值。在 Windows 中,这个更新间隔为 10 毫秒。所以它的颗粒度不超过 10 毫秒或 10000 微妙。

Windows 2000 还有一个 QueryPerformanceCounter() API,它用来重新修正 64 位高分辨率性能计数器的当前值。调用 QueryPerformanceCounter() 的结果中的每次“报时”取决于 QueryPerformanceFrequency() 返回的值。频率是每秒计数器的增加量,所以秒可以表示成:


用 QueryPerformanceCounter() 计算秒


     LARGE_INTEGER tim, freq;
double seconds;
QueryPerformanceCounter(&tim);
QeryPerformanceFrequency(&freq);
seconds = (double)tim / (double) freq;

由于 GetTickCount() 的分辨率太低,我们将只使用 QueryPerformanceCounter。我们必须要注意,如果计时短到与 QueryPerformanceCounter() API 的开销一样时,那么我们的结果可能时不可靠的。下面我们将测量计时例程的开销。

在 Linux 上,使用 gettimeofday() API。只有这个 API 可以满足我们在次毫秒级时间的需求。

选择完要使用的 API 后,需要定义自己的 API,这样可以使程序在不知道主机操作系统的情况下也可以使用这些 API。我们选用下面的接口来实现这些功能:


计时例程接口


     void tstart();
void tend();
double tval();

当调用 Tstart() 时,它记录静态内存中的时间值。当调用 Tend() 时,它记录静态内存中的时间值。Tval() 采用 tstart 和 tend 时间值,将它们转换为双精度数,然后减去它们,以返回双精度形式的结果。这个接口在 Linux 和 Windows 上很容易实现,它执行了所需要的计时功能。

Linux 和 Windows 2000 下计时例程的实现如下。由于不能避免对系统的依赖性,所以我们的目标是在尽可能地减小条件定义的情况下,编写最佳的代码。以下是计时例程的清单。


计时例程


    #ifdef _WIN32
static LARGE_INTEGER _tstart, _tend;
static LARGE_INTEGER freq;
void tstart(void)
{
static int first = 1;
if(first) {
QueryPerformanceFrequency(&freq);
first = 0;
}
QueryPerformanceCounter(&_tstart);
}
void tend(void)
{
QueryPerformanceCounter(&_tend);
}
double tval()
{
return ((double)_tend.QuadPart -
(double)_tstart.QuadPart)/((double)freq.QuadPart);
}
#else
static struct timeval _tstart, _tend;
static struct timezone tz;
void tstart(void)
{
gettimeofday(&_tstart, &tz);
}
void tend(void)
{
gettimeofday(&_tend,&tz);
}
double tval()
{
double t1, t2;
t1 =  (double)_tstart.tv_sec + (double)_tstart.tv_usec/(1000*1000);
t2 =  (double)_tend.tv_sec + (double)_tend.tv_usec/(1000*1000);
return t2-t1;
}
#endif

最后一个的例程是 "char *ver()"。这个简单的函数返回一个描述当前操作系统环境的字符串。正如在源代码中所见,它在每个操作平台上都完全不同。在该例程结尾处是用于测试的条件性定义的 main() 例程。编译过程如下:


将 ver.cpp 编译成为程序


     gcc -DMAIN -O2 ver.cpp -o ver
或
cl -DMAIN -O2 ver.cpp -o ver.exe

ver.exe 程序用于将操作系统版本信息记录到输出文件。


Ver.cpp - 打印操作系统版本

   #ifdef _WIN32
#include <windows.h>
#else
#include <sys/utsname.h>
#endif
#include <stdio.h>
int ver_underbars = 0;
char *ver()
{
char *q;
#ifdef _WIN32
static char verbuf[256];
#else
static char verbuf[4*SYS_NMLN + 4];
#endif
#ifdef _WIN32
OSVERSIONINFO VersionInfo;
VersionInfo.dwOSVersionInfoSize = sizeof(VersionInfo);
if(GetVersionEx(&VersionInfo)) {
if(strlen(VersionInfo.szCSDVersion) > 200)
VersionInfo.szCSDVersion[100] = 0;
sprintf(verbuf, "Windows %d.%d build%d PlatformId %d SP=/"%s/"",
VersionInfo.dwMajorVersion,
VersionInfo.dwMinorVersion,
VersionInfo.dwBuildNumber,
VersionInfo.dwPlatformId,
VersionInfo.szCSDVersion);
}
else {
strcpy(verbuf, "WINDOWS UNKNOWN");
}
#else
struct utsname ubuf;
if(uname(&ubuf)) {
strcpy(verbuf, "LINUX UNKNOWN");
}
else {
sprintf(verbuf,"%s %s %s %s",
ubuf.sysname,
ubuf.release,
ubuf.version,
ubuf.machine);
}
#endif
// Substitute an underbar for white space. Makes output
// easier to parse.
if(ver_underbars) {
for(q = verbuf; *q; q++)
if(*q == ' '  || *q == '/t' || *q == '/n' ||
*q == '/r' || *q == '/b' || *q == '/f')
*q = '_';
}
return verbuf;
}
// gcc -DMAIN ver.cpp -o ver -- produces a simple test program.
#ifdef MAIN
int main(int ac, char *av)
{
if(ac > 1) ver_underbars = 1;
printf("%s/n", ver());
return 0;
}
#endif

上面定义的计时函数可以满足我们的需要。开始使用它们之前,应该知道它们要执行多久。实际上,我们只需要知道 tstart() 和 tend() 要执行多长时间。由于这两个函数在形式上是一样的,因此只需要计算其中一个的执行时间。在 Windows 2000 和 Linux 中,使用 time-timers.cpp 程序对计时函数进行计时分析。请注意,这里只列出了 main() 例程。实际程序包括所有计时器函数和前面清单中的 atoik() 源代码。


time-timers.cpp - 计时定时器的程序


    char *applname;
int main(int ac, char *av[])
{
long count = 100000;
long i;
double t;
char *v = ver();
char *q;
applname = av[0];
if(strrchr(applname,SLASHC))
applname = strrchr(applname,SLASHC) + 1;
if(ac > 1) {
count = atoik(av[1]);
ac--;
av++;
if(count < 0)
count = 100000;
}
tstart();
for(i = 0; i < count; i++)
tend();
tend();
t = tval();
printf("%s: ",applname);
printf("%d calls to tend() = %8.3f seconds %8.3f usec/call/n",
count,
t,
(t/( (double) count ))*1E6);
return 0;
}

一切就绪。现在编译 time-timers.cpp 程序,方式如下:


编译 time-timers.cpp


    在 LINUX 上
gcc -O2 time-timers.cpp -o time-timers
在 Windows 2000 上
cl -O2 time-timers.cpp -o time-timers.exe

这个程序只使用一个可选变量。缺省情况下,程序调用 tend() 函数 100,000 次。重复运行该程序可保证重新生成时间结果。我们使用了缺省计数,运行了该程序 10 次。在 Linux 2.2.16、Linux 2.4.2 和 Windows 2000 的表中分别显示了结果。

在同一台 Thinkpad 上的 Linux 2.2.16、Linux 2.4.2 和 Windows 2000 中,我运行了以下脚本。事实上,最初我使用了 Linux 2.4.2 的对称多处理 (SMP) 版本。我无意中使用了 SMP 版本进行构建和测试。发现这个情况后,我还构建了单处理器版本并用其进行了测试。下面总结了这两个版本的结果(如果有兴趣)。


运行 running time-timers 的脚本

   
ver > time-timers.out
for i in 1 2 3 4 5 6 7 8 9 10
do
time-timers 1m
done >> time-timers.out
for i in 1 2 3 4 5 6 7 8 9 10
do
time-timers 1m
done >> time-timers.out

这个脚本将把对 tend() 的一百万次调用运行 20 遍。结果如下:

Linux 2.2.16 Linux 2.4.2 Linux 2.4.2 SMP Windows 2000
0.740 usec0.729 usec0.806 usec1.945 usec

我能够得出的唯一结论是在 Winsows 2000 里的 QueryPerformanceCounter() 系统调用要比同一硬件上的 gettimeofday() API 慢得多。对于我们的目的来讲,计时例程的 2 微秒的颗粒度是足够的。在 1 毫秒测量时间里,只有千分之二是实际的测量开销。即 0.2%,对我们的目的来说是可接受的范围。





结束语

本文定义了以后在这个系列中将要用到的计时例程,然后讨论了如何用简单的 ver.cpp 程序来标识这个结果,最后测量和总结了计时例程的开销。这个测量结果不可避免地引出了我们正在讨论的操作系统性能特征问题。

通过使用 time-timers.cpp 程序,我们了解到计时例程的开销在 Windows 上少于 2 微秒,在 Linux 上少于 1 微秒。请使用 讨论论坛来发表您关于本文的反应、想法、问题等。





参考资料

  • 单击本文顶部或底部的 “讨论”参加关于本文的 讨论论坛


  • 本专栏中引用的文件:
    • ver.cpp
    • time-timers.cpp

  • Linux 和 Windows NT 之间性能的比较在过去的商业出版物上已出现过。其中大多数都集中在更高级别的操作上。Ziff Davis 发布了两个与此工作相关免费的基准程序:
    • WebBench是用来测量访问 Web 页面。
    • NetBench是用来测量访问网络文件。
    • 在网站上已经发布了从这些基准测量得到的结果和在出版物上的讨论。在 Microsoft.com 上有 最近的评论





关于作者

Edward Bradford 博士目前是 Microsoft Premier Support for IBM Software Group 的负责人,并为 Linux 和 Windows 2000 软件开发人员撰稿每周一次的时事通讯。可以通过 egb@us.ibm.com与他联系。

这篇关于运行时:Linux 和 Windows 2000上的高性能编程技术的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/928797

相关文章

Windows环境下解决Matplotlib中文字体显示问题的详细教程

《Windows环境下解决Matplotlib中文字体显示问题的详细教程》本文详细介绍了在Windows下解决Matplotlib中文显示问题的方法,包括安装字体、更新缓存、配置文件设置及编码調整,并... 目录引言问题分析解决方案详解1. 检查系统已安装字体2. 手动添加中文字体(以SimHei为例)步骤

Linux进程CPU绑定优化与实践过程

《Linux进程CPU绑定优化与实践过程》Linux支持进程绑定至特定CPU核心,通过sched_setaffinity系统调用和taskset工具实现,优化缓存效率与上下文切换,提升多核计算性能,适... 目录1. 多核处理器及并行计算概念1.1 多核处理器架构概述1.2 并行计算的含义及重要性1.3 并

Linux线程之线程的创建、属性、回收、退出、取消方式

《Linux线程之线程的创建、属性、回收、退出、取消方式》文章总结了线程管理核心知识:线程号唯一、创建方式、属性设置(如分离状态与栈大小)、回收机制(join/detach)、退出方法(返回/pthr... 目录1. 线程号2. 线程的创建3. 线程属性4. 线程的回收5. 线程的退出6. 线程的取消7.

Linux下进程的CPU配置与线程绑定过程

《Linux下进程的CPU配置与线程绑定过程》本文介绍Linux系统中基于进程和线程的CPU配置方法,通过taskset命令和pthread库调整亲和力,将进程/线程绑定到特定CPU核心以优化资源分配... 目录1 基于进程的CPU配置1.1 对CPU亲和力的配置1.2 绑定进程到指定CPU核上运行2 基于

golang程序打包成脚本部署到Linux系统方式

《golang程序打包成脚本部署到Linux系统方式》Golang程序通过本地编译(设置GOOS为linux生成无后缀二进制文件),上传至Linux服务器后赋权执行,使用nohup命令实现后台运行,完... 目录本地编译golang程序上传Golang二进制文件到linux服务器总结本地编译Golang程序

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

Linux在线解压jar包的实现方式

《Linux在线解压jar包的实现方式》:本文主要介绍Linux在线解压jar包的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux在线解压jar包解压 jar包的步骤总结Linux在线解压jar包在 Centos 中解压 jar 包可以使用 u

linux解压缩 xxx.jar文件进行内部操作过程

《linux解压缩xxx.jar文件进行内部操作过程》:本文主要介绍linux解压缩xxx.jar文件进行内部操作,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、解压文件二、压缩文件总结一、解压文件1、把 xxx.jar 文件放在服务器上,并进入当前目录#

Linux系统性能检测命令详解

《Linux系统性能检测命令详解》本文介绍了Linux系统常用的监控命令(如top、vmstat、iostat、htop等)及其参数功能,涵盖进程状态、内存使用、磁盘I/O、系统负载等多维度资源监控,... 目录toppsuptimevmstatIOStatiotopslabtophtopdstatnmon

在Linux中改变echo输出颜色的实现方法

《在Linux中改变echo输出颜色的实现方法》在Linux系统的命令行环境下,为了使输出信息更加清晰、突出,便于用户快速识别和区分不同类型的信息,常常需要改变echo命令的输出颜色,所以本文给大家介... 目python录在linux中改变echo输出颜色的方法技术背景实现步骤使用ANSI转义码使用tpu