运行时: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 rm命令误操作的多场景防护方案与实践

《防止Linuxrm命令误操作的多场景防护方案与实践》在Linux系统中,rm命令是删除文件和目录的高效工具,但一旦误操作,如执行rm-rf/或rm-rf/*,极易导致系统数据灾难,本文针对不同场景... 目录引言理解 rm 命令及误操作风险rm 命令基础常见误操作案例防护方案使用 rm编程 别名及安全删除

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

Python实现批量CSV转Excel的高性能处理方案

《Python实现批量CSV转Excel的高性能处理方案》在日常办公中,我们经常需要将CSV格式的数据转换为Excel文件,本文将介绍一个基于Python的高性能解决方案,感兴趣的小伙伴可以跟随小编一... 目录一、场景需求二、技术方案三、核心代码四、批量处理方案五、性能优化六、使用示例完整代码七、小结一、

使用docker搭建嵌入式Linux开发环境

《使用docker搭建嵌入式Linux开发环境》本文主要介绍了使用docker搭建嵌入式Linux开发环境,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录1、前言2、安装docker3、编写容器管理脚本4、创建容器1、前言在日常开发全志、rk等不同

C# LiteDB处理时间序列数据的高性能解决方案

《C#LiteDB处理时间序列数据的高性能解决方案》LiteDB作为.NET生态下的轻量级嵌入式NoSQL数据库,一直是时间序列处理的优选方案,本文将为大家大家简单介绍一下LiteDB处理时间序列数... 目录为什么选择LiteDB处理时间序列数据第一章:LiteDB时间序列数据模型设计1.1 核心设计原则

linux系统上安装JDK8全过程

《linux系统上安装JDK8全过程》文章介绍安装JDK的必要性及Linux下JDK8的安装步骤,包括卸载旧版本、下载解压、配置环境变量等,强调开发需JDK,运行可选JRE,现JDK已集成JRE... 目录为什么要安装jdk?1.查看linux系统是否有自带的jdk:2.下载jdk压缩包2.解压3.配置环境

Linux搭建ftp服务器的步骤

《Linux搭建ftp服务器的步骤》本文给大家分享Linux搭建ftp服务器的步骤,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录ftp搭建1:下载vsftpd工具2:下载客户端工具3:进入配置文件目录vsftpd.conf配置文件4:

Linux实现查看某一端口是否开放

《Linux实现查看某一端口是否开放》文章介绍了三种检查端口6379是否开放的方法:通过lsof查看进程占用,用netstat区分TCP/UDP监听状态,以及用telnet测试远程连接可达性... 目录1、使用lsof 命令来查看端口是否开放2、使用netstat 命令来查看端口是否开放3、使用telnet

Linux系统管理与进程任务管理方式

《Linux系统管理与进程任务管理方式》本文系统讲解Linux管理核心技能,涵盖引导流程、服务控制(Systemd与GRUB2)、进程管理(前台/后台运行、工具使用)、计划任务(at/cron)及常用... 目录引言一、linux系统引导过程与服务控制1.1 系统引导的五个关键阶段1.2 GRUB2的进化优

Linux查询服务器 IP 地址的命令详解

《Linux查询服务器IP地址的命令详解》在服务器管理和网络运维中,快速准确地获取服务器的IP地址是一项基本但至关重要的技能,下面我们来看看Linux中查询服务器IP的相关命令使用吧... 目录一、hostname 命令:简单高效的 IP 查询工具命令详解实际应用技巧注意事项二、ip 命令:新一代网络配置全