运行时: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

相关文章

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

Linux系统中卸载与安装JDK的详细教程

《Linux系统中卸载与安装JDK的详细教程》本文详细介绍了如何在Linux系统中通过Xshell和Xftp工具连接与传输文件,然后进行JDK的安装与卸载,安装步骤包括连接Linux、传输JDK安装包... 目录1、卸载1.1 linux删除自带的JDK1.2 Linux上卸载自己安装的JDK2、安装2.1

Linux卸载自带jdk并安装新jdk版本的图文教程

《Linux卸载自带jdk并安装新jdk版本的图文教程》在Linux系统中,有时需要卸载预装的OpenJDK并安装特定版本的JDK,例如JDK1.8,所以本文给大家详细介绍了Linux卸载自带jdk并... 目录Ⅰ、卸载自带jdkⅡ、安装新版jdkⅠ、卸载自带jdk1、输入命令查看旧jdkrpm -qa

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

Linux安装MySQL的教程

《Linux安装MySQL的教程》:本文主要介绍Linux安装MySQL的教程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux安装mysql1.Mysql官网2.我的存放路径3.解压mysql文件到当前目录4.重命名一下5.创建mysql用户组和用户并修

Linux上设置Ollama服务配置(常用环境变量)

《Linux上设置Ollama服务配置(常用环境变量)》本文主要介绍了Linux上设置Ollama服务配置(常用环境变量),Ollama提供了多种环境变量供配置,如调试模式、模型目录等,下面就来介绍一... 目录在 linux 上设置环境变量配置 OllamPOgxSRJfa手动安装安装特定版本查看日志在

Linux系统之主机网络配置方式

《Linux系统之主机网络配置方式》:本文主要介绍Linux系统之主机网络配置方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、查看主机的网络参数1、查看主机名2、查看IP地址3、查看网关4、查看DNS二、配置网卡1、修改网卡配置文件2、nmcli工具【通用

Linux系统之dns域名解析全过程

《Linux系统之dns域名解析全过程》:本文主要介绍Linux系统之dns域名解析全过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、dns域名解析介绍1、DNS核心概念1.1 区域 zone1.2 记录 record二、DNS服务的配置1、正向解析的配置