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

相关文章

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n

[Linux]:进程(下)

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:Linux学习 贝蒂的主页:Betty’s blog 1. 进程终止 1.1 进程退出的场景 进程退出只有以下三种情况: 代码运行完毕,结果正确。代码运行完毕,结果不正确。代码异常终止(进程崩溃)。 1.2 进程退出码 在编程中,我们通常认为main函数是代码的入口,但实际上它只是用户级