RDTSCP 是否跨多核单调递增?
Does RDTSCP increment monotonically across multi-cores?
我很困惑 rdtscp
是否在多核环境中单调递增。根据文档:__rdtscp,rdtscp
似乎是基于处理器的指令,可以防止调用周围的指令重新排序。
The processor monotonically increments the time-stamp counter MSR every clock cycle and resets it to 0 whenever the processor is reset.
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;
}
所以,这里有两个问题:
- 我的理解对不对?
- 如何构造一个例子来验证它?
==========已更新,根据评论
__rdtscp 将(总是?)在高级 cpus
上跨内核递增
在大多数系统上是的,如果您在线程之间创建同步以确保一个线程确实运行在另一个线程之后执行1.否则所有的赌注都没有了;在另一个线程之前启动一个线程并不能确保它的代码首先执行。
脚注 1:例如让一个旋转等待直到它看到另一个完成的原子存储。或者在临界区使用互斥量和运行 rdtscp
,以及一个变量来记录另一个线程是否已经存在。
在任何非古老的东西上(至少像 Core2 和更新的),TSC 以恒定频率(“参考”)频率滴答。有关 constant_tsc
/ nonstop_tsc
CPU 功能的链接和详细信息,以及 TSC 未同步的可能性,请参阅 this answer。
实际上大多数现代系统确实在内核之间同步了 TSC 我认为,感谢主板供应商确保即使在多插槽系统上,RESET 信号也会分配给所有内核核心同时。固件和 OS 软件注意不要搞砸。在单插槽系统上要容易得多,例如具有多核 CPU 的普通台式机,其中所有“额外”内核都在同一芯片上。
但这 不能保证, rdtscp
存在的部分原因(带有处理器 ID 输出)是这种可能性(我认为这可能更常见在 RDTSCP 是新的旧系统上)。
VM 甚至可以使用 CPU 功能来透明地偏移和缩放 TSC(在 HW 支持下),在物理机器之间迁移 VM,同时保持 TSC 的单调性和频率。不加选择地使用这些功能当然会产生不同步的 TSC,甚至会在不同内核上以不同频率产生 运行。
TSC 是一个 64 位计数器,通常以 CPUs 额定标签频率计数。在某些 CPUs 上,这可能超过 ~4.2 GHz (2^32),因此在快速 CPUs 上,高半部分大约每秒递增一次。 理论上,如果计算机已“启动”超过 2^32 秒(几十年),或者如果 TSC 已手动设置为具有较大偏移量,则 TSC 理论上可以回绕。
我很困惑 rdtscp
是否在多核环境中单调递增。根据文档:__rdtscp,rdtscp
似乎是基于处理器的指令,可以防止调用周围的指令重新排序。
The processor monotonically increments the time-stamp counter MSR every clock cycle and resets it to 0 whenever the processor is reset.
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;
}
所以,这里有两个问题:
- 我的理解对不对?
- 如何构造一个例子来验证它?
==========已更新,根据评论
__rdtscp 将(总是?)在高级 cpus
上跨内核递增在大多数系统上是的,如果您在线程之间创建同步以确保一个线程确实运行在另一个线程之后执行1.否则所有的赌注都没有了;在另一个线程之前启动一个线程并不能确保它的代码首先执行。
脚注 1:例如让一个旋转等待直到它看到另一个完成的原子存储。或者在临界区使用互斥量和运行 rdtscp
,以及一个变量来记录另一个线程是否已经存在。
在任何非古老的东西上(至少像 Core2 和更新的),TSC 以恒定频率(“参考”)频率滴答。有关 constant_tsc
/ nonstop_tsc
CPU 功能的链接和详细信息,以及 TSC 未同步的可能性,请参阅 this answer。
实际上大多数现代系统确实在内核之间同步了 TSC 我认为,感谢主板供应商确保即使在多插槽系统上,RESET 信号也会分配给所有内核核心同时。固件和 OS 软件注意不要搞砸。在单插槽系统上要容易得多,例如具有多核 CPU 的普通台式机,其中所有“额外”内核都在同一芯片上。
但这 不能保证, rdtscp
存在的部分原因(带有处理器 ID 输出)是这种可能性(我认为这可能更常见在 RDTSCP 是新的旧系统上)。
VM 甚至可以使用 CPU 功能来透明地偏移和缩放 TSC(在 HW 支持下),在物理机器之间迁移 VM,同时保持 TSC 的单调性和频率。不加选择地使用这些功能当然会产生不同步的 TSC,甚至会在不同内核上以不同频率产生 运行。
TSC 是一个 64 位计数器,通常以 CPUs 额定标签频率计数。在某些 CPUs 上,这可能超过 ~4.2 GHz (2^32),因此在快速 CPUs 上,高半部分大约每秒递增一次。 理论上,如果计算机已“启动”超过 2^32 秒(几十年),或者如果 TSC 已手动设置为具有较大偏移量,则 TSC 理论上可以回绕。