#include <stdio.h>
static inline unsigned long long tick()
{
unsigned long long d;
__asm__ __volatile__ ("rdtsc" : "=A" (d) );
return d;
}
int main()
{
long long res;
res=tick();
res=tick()-res;
printf("%d",res);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我用gcc编译了这段代码,并使用了-O0 -O1 -O2 -O3优化.我总是得到2000-2500个周期.任何人都可以解释这个输出的原因吗?如何度过这些周期?
第一个函数"tick"是错误的.这是对的.
另一个版本的功能"滴答"
static __inline__ unsigned long long tick()
{
unsigned hi, lo;
__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}
Run Code Online (Sandbox Code Playgroud)
这是-O3的汇编代码
.file "rdtsc.c"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "%d"
.text
.p2align 4,,15
.globl …Run Code Online (Sandbox Code Playgroud) 我正在研究使用x86 CPU中的时间戳寄存器(TSR)来测量基准性能.它是一个有用的寄存器,因为它以单调时间单位测量,不受时钟速度变化的影响.很酷.
这是一份英特尔文档,显示了使用TSR进行可靠基准测试的asm片段,包括使用cpuid进行管道同步.见第16页:
要读取开始时间,它说(我注释了一下):
__asm volatile (
"cpuid\n\t" // writes e[abcd]x
"rdtsc\n\t" // writes edx, eax
"mov %%edx, %0\n\t"
"mov %%eax, %1\n\t"
//
:"=r" (cycles_high), "=r" (cycles_low) // outputs
: // inputs
:"%rax", "%rbx", "%rcx", "%rdx"); // clobber
Run Code Online (Sandbox Code Playgroud)
我不知道为什么暂存寄存器用来取的价值观edx
和eax.为什么不删除MOVS和读取TSR值右出的edx
和eax?像这样:
__asm volatile(
"cpuid\n\t"
"rdtsc\n\t"
//
: "=d" (cycles_high), "=a" (cycles_low) // outputs
: // inputs
: "%rbx", "%rcx"); // clobber
Run Code Online (Sandbox Code Playgroud)
通过这样做,您可以保存两个寄存器,从而降低C编译器需要溢出的可能性.
我对吗?或者那些MOV在某种程度上是战略性的?
(我同意你确实需要临时寄存器来读取停止时间,因为在那种情况下指令的顺序是相反的:你有rdtscp,...,cpuid.cpuid指令破坏了rdtscp的结果).
谢谢
给定具有恒定TSC的x86 ,这对于测量实时非常有用,如何在启动时使用Linux计算的TSC校准因子在TSC参考周期的"单位"和正常人类实时单位(如纳秒)之间进行转换?
也就是说,当然可以通过CLOCK_MONOTONIC在某个时间间隔的两端进行TSC和时钟测量(例如,用)来计算用户区中的TSC频率,以确定TSC频率,但Linux已经在启动时进行了此计算,因为它内部使用TSC帮助进行计时.
例如,您可以通过以下方式查看内核的结果dmesg | grep tsc:
[ 0.000000] tsc: PIT calibration matches HPET. 2 loops
[ 0.000000] tsc: Detected 3191.922 MHz processor
[ 1.733060] tsc: Refined TSC clocksource calibration: 3192.007 MHz
Run Code Online (Sandbox Code Playgroud)
在更糟糕的情况下,我猜你可以尝试dmesg在运行时grep结果,但坦率地看起来很可怕,脆弱和各种各样的坏0.
使用内核确定的校准时间的优点很多:
cpuid叶片0x15 宣传其TSC频率,因此不一定需要进行校准)时,您会自动在TSC校准中获取新技术.gettimeofday和clock_gettime1)使用的TSC频率在某种程度上"一致" .然而,使用Linux的TSC校准的一些缺点包括:
0例如:系统可能没有dmesg安装,您可能无法以普通用户身份运行它,累积的输出可能已经缠绕,因此线路不再存在,您可能会在grep上获得误报,内核消息是英文散文,可能会有变化,可能很难启动子流程等等.
1这是否有争议 - 但如果您将rdtsc调用与使用OS时间保持的代码混合,则可能会提高精度.
我有一个具有以下代码的汇编程序.此代码可编译为intel处理器.但是,当我使用PPC(交叉)编译器时,我收到一个错误,即操作码无法识别.我试图找出PPC架构是否有等效的操作码.
.file "assembly.s"
.text
.globl func64
.type func64,@function
func64:
rdtsc
ret
.size func64,.Lfe1-func64
.globl func
.type func,@function
func:
rdtsc
ret
Run Code Online (Sandbox Code Playgroud) 我试图使用RDTSC,但似乎我的方法可能是错误的获得核心速度:
#include "stdafx.h"
#include <windows.h>
#include <process.h>
#include <iostream>
using namespace std;
struct Core
{
int CoreNumber;
};
static void startMonitoringCoreSpeeds(void *param)
{
Core core = *((Core *)param);
SetThreadAffinityMask(GetCurrentThread(), 1 << core.CoreNumber);
while (true)
{
DWORD64 first = __rdtsc();
Sleep(1000);
DWORD64 second = __rdtsc();
cout << "Core " << core.CoreNumber << " has frequency " << ((second - first)*pow(10, -6)) << " MHz" << endl;
}
}
int GetNumberOfProcessorCores()
{
DWORD process, system;
if (GetProcessAffinityMask(GetCurrentProcess(), &process, &system))
{
int count …Run Code Online (Sandbox Code Playgroud) 我正在研究RDTSC,并了解它是如何虚拟化虚拟机,如VirtualBox和VMWare.为什么Intel/AMD会遇到虚拟化此指令的麻烦?
我觉得它可以很容易地用陷阱来模拟它并不是一个超常用的指令(我测试过并且在禁用硬件RDTSC虚拟化的虚拟机中一般用法没有明显的减速).
但是,我知道英特尔/ AMD不会把这个指令添加到虚拟化硬件中,除非能够非常快速地执行它是很重要的.
有谁知道为什么?
多年来,x86 CPU 都支持该rdtsc指令,该指令读取当前 CPU 的“时间戳计数器”。这个计数器的确切定义随着时间的推移而改变,但在最近的 CPU 上,它是一个相对于挂钟时间以固定频率递增的计数器,因此它作为快速、准确时钟或测量时间的构建块非常有用由小段代码获取。
关于rdtsc指令的一个重要事实没有以任何特殊方式与周围的代码一起排序。像大多数指令一样,它可以相对于与它没有依赖关系的其他指令自由地重新排序。这实际上是“正常的”,对于大多数指令,它只是一种使 CPU 更快的几乎不可见的方式(这只是一种长篇大论的无序执行方式)。
因为rdtsc它很重要,因为这意味着您可能没有为您期望的代码计时。例如,给定以下序列1:
rdtsc
mov ecx, eax
mov rdi, [rdi]
mov rdi, [rdi]
rdtsc
Run Code Online (Sandbox Code Playgroud)
您可能希望rdtsc测量追逐加载负载的两个指针的延迟mov rdi, [rdi]。然而,在实践中,即使这两个加载都需要查看时间(如果它们在缓存中丢失,则为 100 秒),您将获得相当小的读取值rdtsc。问题是第二个rdtsc不等待加载完成,它只是乱序执行,所以你没有按你认为的时间间隔计时。也许这两rdtsc条指令实际上甚至在第一次加载开始之前就执行了,这取决于rdi在此示例之前的代码中是如何计算的。
到目前为止,这听起来更像是对一个没人问的问题的回答,而不是一个真正的问题,但我已经到了那里。
您有两个基本用例rdtsc:
作为一种精确的计时机制,例如,在微基准测试中。在这种情况下,您通常会rdtsc根据lfence说明防止重新订购。对于上面的示例,您可能会执行以下操作:
lfence
rdtsc
lfence
mov ecx, eax
...
lfence
rdtsc
Run Code Online (Sandbox Code Playgroud)
确保定时指令 ( ...) 不会逃逸到定时区域之外,并确保来自时间区域内的指令不会进入(可能问题不大,但它们可能会与您想要的代码竞争资源测量)。
多年后,英特尔看不起我们这些可怜的程序员,并提出了一条新指令:rdtscp. 就像rdtsc它返回时间戳计数器的读数一样,这家伙做了更多的事情:它使用时间戳读数原子地读取特定于内核的 MSR …
我从事编程语言分析器工作,正在寻找分辨率优于 100 ns 的 Windows 计时器解决方案。
QueryPerformanceCounter应该是一个答案,但在 Windows 10 上返回的频率为QueryPerformanceFrequency10 MHz,在 Windows 7 上甚至更低
GetSystemTimePreciseAsFileTime有 100 ns 刻度/步
RDTSC分辨率优于1ns,但随频率变化
我的目标分辨率至少为 10 ns。
目前最好的解决方案是什么?
如何QueryPerformanceCounter实施?是否可以轻松拆卸并提高分辨率?
是否可以RDTSC在每次频率变化时直接使用并跟踪/中断?
假设我们有一些相同的汇编的重复,其中包含RDTSC诸如
volatile size_t tick1;
asm ( "rdtsc\n" // Returns the time in EDX:EAX.
"shl $32, %%rdx\n" // Shift the upper bits left.
"or %%rdx, %q0" // 'Or' in the lower bits.
: "=a" (tick1)
:
: "rdx");
this_thread::sleep_for(1s);
volatile size_t tick2;
asm ( "rdtsc\n" // clang's optimizer just thinks this asm yields
"shl $32, %%rdx\n" // the same bits as above, so it just loads
"or %%rdx, %q0" // the result to qword ptr [rsp + 8]
: …Run Code Online (Sandbox Code Playgroud)