首页 技术 正文
技术 2022年11月12日
0 收藏 873 点赞 3,973 浏览 3238 个字

多核时代不宜再用 x86 的 RDTSC 指令测试指令周期和时间

陈硕
Blog.csdn.net/Solstice

自从 Intel Pentium 加入 RDTSC 指令以来,这条指令是 micro-benchmarking
的利器,可以以极小的代价获得高精度的 CPU 时钟周期数(Time Stamp
Counter),不少介绍优化的文章[1]和书籍用它来比较两段代码的快慢。甚至有的代码用 RDTSC 指令来计时,以替换
gettimeofday() 之类的系统调用。在多核时代,RDTSC 指令的准确度大大削弱了,原因有三:

  1. 不能保证同一块主板上每个核的 TSC 是同步的;
  2. CPU 的时钟频率可能变化,例如笔记本电脑的节能功能;
  3. 乱序执行导致 RDTSC 测得的周期数不准,这个问题从 Pentium Pro 时代就存在。

这些都影响了 RDTSC 的两大用途,micro-benchmarking 和计时。

RDTSC 一般的用法是,先后执行两次,记下两个 64-bit 整数 start 和 end,那么 end-start 代表了这期间 CPU 的时钟周期数。

在多核下,这两次执行可能会在两个 CPU 上发生,而这两个 CPU
的计数器的初值不一定相同(由于完成上电复位的准确时机不同),(有办法同步,见[3]),那么就导致 micro-benchmarking
的结果包含了这个误差,这个误差可正可负,取决于先执行的那块 CPU 的时钟计数器是超前还是落后。

另外,对于计时这个用途,时间 = 周期数 / 频率,由于频率可能会变(比如我的笔记本的 CPU 通常半速运行在
800MHz,繁忙的时候全速运行在 1.6GHz),那么测得的时间也就不准确了。有的新 CPU 的 RDTSC
计数频率是恒定的,那么时钟是准了,那又会导致 micro-benchmarking 的结果不准,见
[2]。还有一个可能是掉电之后恢复(比如休眠),那么 TSC 会清零。 总之,用 RDTSC 来计时是不灵的。

乱序执行这个问题比较简单 [1],但意义深远:在现代 CPU 的复杂架构下,测量几条或几十条指令的耗时是无意义的,因为观测本身会干扰
CPU 的执行(cache, 流水线, 多发射,乱序, 猜测),这听上去有点像量子力学系统了。要么我们以更宏观的指标来标示性能,把”花 xxx
个时钟周期”替换”每秒处理 yyy 条消息”或”消息处理的延时为 zzz 毫秒”;要么用专门的 profiler 来减小对观测结果的影响(无论是
callgrind 这种虚拟 CPU,还是 OProfile 这种采样器)。

虽然 RDTSC 废掉了,性能测试用的高精度计时还是有办法的 [2],在 Windows
用 QueryPerformanceCounter 和 QueryPerformanceFrequency,Linux 下用 POSIX
的 clock_gettime 函数,以 CLOCK_MONOTONIC 参数调用。或者按文献 [3] 的办法,先同步 TSC,
再使用它。(我不知道现在最新的 Linux 官方内核是不是内置了这个同步算法。也不清楚校准后的两个 CPU 的“钟”会不会再次失步。)

通过调用SetThreadAffinityMask,就能为各个线程设置亲缘性屏蔽:

  DWORD_PTR 
SetThreadAffinityMask  ( 
     
HANDLE  hThread, 
     
     
     
     
     
  //  handle 
to  thread 
     
DWORD_PTR  dwThreadAffinityMask 
  //  thread 
affinity  mask 
  ); 
  该函数中的 
hThread  参数用于指明要限制哪个线程, 
dwThreadAffinityMask用于指明该线程 
 
能够在哪个CPU上运行。dwThreadAffinityMask必须是进程的亲缘性屏蔽的相应子集。返回值

是线程的前一个亲缘性屏蔽。例如,可能有一个包含4个线程的进程,它们在拥有4个CPU的计算机上运行。如果这些线程中的一个线程正在执行非常重要的操作,而你想增加某个CPU始终可供它使用的可能性,为此你对其他3个线程进行了限制,使它们不能在CPU 
0上运行,而只能在CPU 
1、2和3上运行。因此,若要将3个线程限制到CPU 
1、2和3上去运行,可以这样操作: 
   
  //线程0只能在cpu 
0上运行 
 
SetThreadAffinityMask(hThread0,0x00000001); 
  //线程1,2,3只能在cpu 
1,2,3上运行 
 
SetThreadAffinityMask(hThread1,0x0000000E); 
 
SetThreadAffinityMask(hThread2,0x0000000E); 
 
SetThreadAffinityMask(hThread3,0x0000000E);   本文对Windows平台下常用的计时函数进行总结,包括精度为秒、毫秒、微秒三种精度的 5种方法。分为在标准C/C++下的二种time()及clock(),标准C/C++所以使用的time()及clock()不仅可以用在 Windows系统,也可以用于Linux系统。在Windows系统下三种,使用Windows提供的API接口timeGetTime()、 GetTickCount()及QueryPerformanceCounter()来完成。文章最后给出了5种计时方法示例代码。

标准C/C++的二个计时函数time()及clock()

time_t time(time_t *timer);

返回以格林尼治时间(GMT)为标准,从1970年1月1日00:00:00到现在的此时此刻所经过的秒数。

time_t实际是个long长整型typedef long time_t;

头文件:#include <time.h>

clock_t clock(void);

返回进程启动到调用函数时所经过的CPU时钟计时单元(clock tick)数,在MSDN中称之为挂钟时间(wal-clock),以毫秒为单位。

clock_t实际是个long长整型typedef long clock_t;

头文件:#include <time.h>

Windows系统API函数

timeGetTime()、GetTickCount()及QueryPerformanceCounter()

DWORD timeGetTime(VOID);

返回系统时间,以毫秒为单位。系统时间是从系统启动到调用函数时所经过的毫秒数。注意,这个值是32位的,会在0到2^32之间循环,约49.71天。

头文件:#include <Mmsystem.h>

引用库:#pragma comment(lib, “Winmm.lib”)

DWORD WINAPI GetTickCount(void);

这个函数和timeGetTime()一样也是返回系统时间,以毫秒为单位。

头文件:直接使用#include <windows.h>就可以了。

高精度计时,以微秒为单位(1毫秒=1000微秒)。

先看二个函数的定义

BOOL QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount);

得到高精度计时器的值(如果存在这样的计时器)。

BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);

返回硬件支持的高精度计数器的频率(次每秒),返回0表示失败。

再看看LARGE_INTEGER

它其实是一个联合体,可以得到__int64 QuadPart;也可以分别得到低32位DWORD LowPart和高32位的值LONG HighPart。

在使用时,先使用QueryPerformanceFrequency()得到计数器的频率,再计算二次调用QueryPerformanceCounter()所得的计时器值之差,用差去除以频率就得到精确的计时了。

头文件:直接使用#include <windows.h>就可以了。

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,492
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,907
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,740
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,495
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,132
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,297