RDTSCP 是否跨多核单调递增?

问题描述

我很困惑 rdtscp 在多核环境中是否单调递增。根据文档:__rdtscprdtscp 似乎是基于处理器的指令,可以防止调用周围的指令重新排序。

处理器在每个时钟周期单调递增时间戳计数器 MSR,并在处理器复位时将其复位为 0。

rdtscp 在同一个 cpu 内核上肯定是单调递增的,但是这个 rdtscp 时间戳是否保证在不同的 cpu 内核上是单调的?我相信没有这种绝对的保证。例如,

Thread on cpu core#0                   Thread on cpu core#1

unsigned int ui;
uint64_t t11 = __rdtscp(&ui); 
uint64_t t12 = __rdtscp(&ui);  
uint64_t t13 = __rdtscp(&ui);         
                                       unsigned int ui;
                                       uint64_t t21 = __rdtscp(&ui);
                                       uint64_t t22 = __rdtscp(&ui);
                                       uint64_t t23 = __rdtscp(&ui);

据我了解,我们可以得出一个决定性的结论t13 > t12 > t11,但我们不能保证t21 > t13

我想写一个脚本来测试我的理解是否正确,但我不知道如何构建一个例子来验证我的假设。

// file name: rdtscptest.cpp
// g++ rdtscptest.cpp -g -lpthread -Wall -O0 -o run
#include <chrono>
#include <thread>
#include <iostream>
#include <string>
#include <string.h>
#include <vector>
#include <x86intrin.h>

using namespace std;

void test(int tid) {
    std::this_thread::sleep_for (std::chrono::seconds (tid));
    unsigned int ui;
    uint64_t tid_unique_ = __rdtscp(&ui);
    std::cout << "tid: " << tid << ",counter: " << tid_unique_ << ",ui: " << ui << std::endl;
    std::this_thread::sleep_for (std::chrono::seconds (1));
}

int main() {
    size_t trd_cnt = 3 ;
    std::vector<std::thread> threads(trd_cnt);

    for (size_t i=0; i< trd_cnt; i++) {
        // three threads with tid: 0,1,2
        // force different threads to run on different cpu cores
        threads[i] = std::thread(test,i);  
        cpu_set_t cpuset;
        cpu_ZERO(&cpuset);
        cpu_SET(i,&cpuset);
        int rc = pthread_setaffinity_np(threads[i].native_handle(),sizeof(cpu_set_t),&cpuset);
        if (rc != 0) {
            std::cout << "Error calling pthread_setaffinity_np,code: " << rc << "\n";
        }
    }

    for (size_t i=0; i< trd_cnt; i++) {
        threads[i].join() ;
    }

    return 0;
}

所以,这里有两个问题:

  1. 我的理解是否正确?
  2. 如何构建一个示例来验证它?

==========根据评论更新

__rdtscp 将(总是?)在高级 cpu 上的内核之间递增

解决方法

在大多数系统上是的,如果您在线程之间创建同步以确保一个线程确实在另一个线程之后运行1。否则所有赌注都将取消;在另一个线程之前启动一个线程并不能确保它的代码首先执行。

脚注 1:例如有一个旋转等待,直到它看到另一个完成的原子存储。或者使用互斥锁并在临界区运行 rdtscp,以及一个变量来记录另一个线程是否已经存在。


在任何非古代(至少像 Core2 和更新版本)上,TSC 以恒定频率(“参考”)频率滴答声。有关 constant_tsc / nonstop_tsc CPU 功能的链接和详细信息,以及 TSC 未同步的可能性,请参阅 this answer

实践中的大多数现代系统确实在内核之间同步了 TSC 我认为,这要归功于主板供应商确保即使在多路系统上,RESET 信号也能同时分配到所有内核.固件和操作系统软件小心不要搞砸。在单插槽系统(如带有多核 CPU 的普通台式机)上,所有“额外”内核都在同一芯片上要容易得多。

但这是不能保证,并且 rdtscp 存在(带有处理器 ID 输出)的部分原因是这种可能性(我认为这在 RDTSCP 时在旧系统上可能更常见)是新的)。

虚拟机甚至可以使用 CPU 功能来透明地偏移和扩展 TSC(有硬件支持),在物理机之间迁移虚拟机,同时保持 TSC 的单调性和频率。不加选择地使用这些功能当然会产生不同步的 TSC,甚至会产生在不同内核上以不同频率运行的 TSC。


TSC 是一个 64 位计数器,通常以 CPU 额定标签频率计数。在某些 CPU 上,这可能超过 ~4.2 GHz (2^32),因此在快速 CPU 上,高半部分每秒增加一次。 如果计算机已经“启动”超过 2^32 秒(几十年),或者如果 TSC 已被手动设置为具有较大的偏移量,TSC 理论上可以回绕