Linux 为什么时钟变得如此不稳定? 简介
旧问题部分包含初始问题(此后添加了进一步的调查和结论)Linux 为什么时钟变得如此不稳定? 简介,linux,time,profiling,Linux,Time,Profiling,旧问题部分包含初始问题(此后添加了进一步的调查和结论) 跳到下面的“进一步调查”部分,详细比较不同的计时方法(rdtsc、clock_gettime和QueryThreadCycleTime) 我相信CGT的不稳定行为可以归因于有缺陷的内核或有缺陷的CPU(见结论部分) 用于测试的代码位于该问题的底部(见附录一节) 抱歉,时间太长了 老问题 简而言之:我使用clock\u gettime来测量许多代码段的执行时间。我在两次运行之间遇到了非常不一致的测量结果。与其他方法相比,该方法具有极高的标
rdtsc
、clock_gettime
和QueryThreadCycleTime
)老问题 简而言之:我使用
clock\u gettime
来测量许多代码段的执行时间。我在两次运行之间遇到了非常不一致的测量结果。与其他方法相比,该方法具有极高的标准偏差(见下文解释)
问题:与其他方法相比,clock\u gettime
会给出如此不一致的测量结果,有什么原因吗?是否有一种具有相同分辨率的替代方法来解释线程空闲时间
解释:我试图分析C代码的一些小部分。每个代码段的执行时间不超过几微秒。在一次运行中,每个代码段将执行数百次,这将产生运行×数百次测量
我还必须只测量线程实际执行的时间(这就是rdtsc
不合适的原因)。我还需要高分辨率(这就是为什么times
不合适的原因)
我尝试了以下方法:
rdtsc
(在Linux和Windows上)
clock\u gettime
(在Linux上带有“clock\u THREAD\u CPUTIME\u ID”),以及
QueryThreadCycleTime
(在Windows上)
方法学:分析进行了25次。在每次运行中,单独的代码段重复101次。因此,我有2525次测量。然后我查看测量值的直方图,并计算一些基本数据(如平均值、标准偏差、中值、模式、最小值和最大值)
我没有说明我是如何衡量这三种方法的“相似性”的,但这仅仅涉及到对每个代码段花费的时间比例的基本比较(“比例”表示时间是标准化的)。然后我看看这些比例的纯粹差异。这一比较表明,当在25次运行中取平均值时,所有“rdtsc”、“QTCT”和“CGT”测量的比例相同。然而,下面的结果表明,“CGT”具有非常大的标准偏差。这使得它在我的用例中无法使用
结果:
相同代码段的clock_gettime
与rdtsc
比较(25次101次测量=2525次读数):
- 时钟时间:
- 1881次11纳秒的测量
- 595个测量值(几乎正态分布)在3369和3414纳秒之间
- 2次11680纳秒的测量
- 1测量1506022纳秒,以及
- 其余的在900到5000纳秒之间
- 最小:11纳秒
- 最大值:1506022纳秒
- 平均值:1471.862纳秒
- 中位数:11纳秒
- 模式:11纳秒
- 标准差饷租值:29991.034
- rdtsc(注意:此运行过程中未发生上下文切换,但如果发生,通常只会产生30000个左右的单个测量值):
- 1178个测量值介于274和325个刻度之间
- 306个测量值介于326和375个刻度之间
- 910个测量值介于376和425个刻度之间
- 129个测量值介于426和990个刻度之间
- 1测量1240个滴答声,以及
- 1测量1256个蜱
- 最小:274蜱
- 最大值:1256蜱
- 平均值:355.806蜱
- 中位数:333蜱
- 模式:376滴答声
- 标准差饷租值:83.896
讨论:
rdtsc
在Linux和Windows上给出了非常相似的结果。它有一个可接受的标准偏差——它实际上相当一致/稳定。但是,它不考虑线程空闲时间。因此,上下文切换会使测量结果变得不稳定(在Windows上,我经常观察到这一点:平均为1000个左右的代码段会时不时地占用30000个左右的时间,这肯定是因为抢占)
QueryThreadCycleTime
提供了非常一致的测量结果,即与rdtsc
相比,标准偏差要低得多。当没有发生上下文切换时,此方法几乎与rdtsc
相同
- 另一方面,clock_gettime
产生了极不一致的结果(不仅在运行之间,而且在测量之间)。标准偏差是极端的(与rdtsc
相比)
我希望统计数字没问题。但是,这两种方法的测量结果如此不一致的原因是什么?当然,还有缓存、CPU/核心迁移等。但这些都不应该对“rdtsc”和“clock_gettime”之间的任何差异负责。发生了什么事
进一步调查 我对此做了进一步的调查。我做了两件事:
clock\u gettime(clock\u THREAD\u CPUTIME\u ID,&t)
的开销(参见附录中的代码1),以及clock\u gettime
的普通循环中,将读数存储到一个数组中(参见附录中的代码2)。我测量增量时间(连续测量时间的差异,这应该与时钟的调用开销相对应)
Range | Frequency
------------------+-----------
697 < x ≤ 800 -> 78111 <-- cached?
800 < x ≤ 1000 -> 16412
1000 < x ≤ 1500 -> 3
1500 < x ≤ 2000 -> 4836 <-- uncached?
2000 < x ≤ 3000 -> 305
3000 < x ≤ 5000 -> 161
5000 < x ≤ 10000 -> 105
10000 < x ≤ 15000 -> 53
15000 < x ≤ 20000 -> 8
20000 < x -> 5
Range | Frequency
--------------------+-----------
x ≤ 1 -> 86738 <-- cached?
282 < x ≤ 300 -> 13118 <-- uncached?
300 < x ≤ 440 -> 78
2000 < x ≤ 5000 -> 52
5000 < x ≤ 30000 -> 5
3000000 < x -> 8
Range | Frequency
------------------+-----------
34 < x ≤ 35 -> 16240 <-- cached?
41 < x ≤ 42 -> 63585 <-- uncached? (small difference)
48 < x ≤ 49 -> 19779 <-- uncached?
49 < x ≤ 120 -> 195
3125 < x ≤ 5000 -> 144
5000 < x ≤ 10000 -> 45
10000 < x ≤ 20000 -> 9
20000 < x -> 2
Range | Frequency
------------------+-----------
13 < x ≤ 14 -> 192
14 < x ≤ 21 -> 78172 <-- cached?
21 < x ≤ 50 -> 10818
50 < x ≤ 103 -> 10624 <-- uncached?
5825 < x ≤ 6500 -> 88
6500 < x ≤ 8000 -> 88
8000 < x ≤ 10000 -> 11
10000 < x ≤ 15000 -> 4
15000 < x ≤ 16372 -> 2
Range | Frequency
------------------+-----------
879 < x ≤ 890 -> 71347 <-- cached?
895 < x ≤ 1469 -> 844
1469 < x ≤ 1600 -> 27613 <-- uncached?
1600 < x ≤ 2000 -> 55
2000 < x ≤ 4000 -> 86
4000 < x ≤ 8000 -> 43
8000 < x ≤ 16000 -> 10
16000 < x -> 1
#include <time.h>
#include <stdio.h>
#include <stdint.h>
/* Compiled & executed with:
gcc clock_gettime_overhead.c -O0 -lrt -o clock_gettime_overhead
./clock_gettime_overhead 100000
*/
int main(int argc, char **args) {
struct timespec tstart, tend, dummy;
int n, N;
N = atoi(args[1]);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tstart);
for (n = 0; n < N; ++n) {
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &dummy);
}
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &tend);
printf("Estimated overhead: %lld ns\n",
((int64_t) tend.tv_sec * 1000000000 + (int64_t) tend.tv_nsec
- ((int64_t) tstart.tv_sec * 1000000000
+ (int64_t) tstart.tv_nsec)) / N / 10);
return 0;
}
#include <time.h>
#include <stdio.h>
#include <stdint.h>
/* Compiled & executed with:
gcc clock_gettime_delta.c -O0 -lrt -o clock_gettime_delta
./clock_gettime_delta > results
*/
#define N 100000
int main(int argc, char **args) {
struct timespec sample, results[N];
int n;
for (n = 0; n < N; ++n) {
clock_gettime(CLOCK_THREAD_CPUTIME_ID, &sample);
results[n] = sample;
}
printf("%s\t%s\n", "Absolute time", "Delta");
for (n = 1; n < N; ++n) {
printf("%lld\t%lld\n",
(int64_t) results[n].tv_sec * 1000000000 +
(int64_t)results[n].tv_nsec,
(int64_t) results[n].tv_sec * 1000000000 +
(int64_t) results[n].tv_nsec -
((int64_t) results[n-1].tv_sec * 1000000000 +
(int64_t)results[n-1].tv_nsec));
}
return 0;
}
static uint64_t rdtsc() {
#if defined(__GNUC__)
# if defined(__i386__)
uint64_t x;
__asm__ volatile (".byte 0x0f, 0x31" : "=A" (x));
return x;
# elif defined(__x86_64__)
uint32_t hi, lo;
__asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
return ((uint64_t)lo) | ((uint64_t)hi << 32);
# else
# error Unsupported architecture.
# endif
#elif defined(_MSC_VER)
return __rdtsc();
#else
# error Other compilers not supported...
#endif
}
#include <stdio.h>
#include <stdint.h>
#include "rdtsc.h"
/* Compiled & executed with:
gcc rdtsc_delta.c -O0 -o rdtsc_delta
./rdtsc_delta > rdtsc_delta_results
Windows:
cl -Od rdtsc_delta.c
rdtsc_delta.exe > windows_rdtsc_delta_results
*/
#define N 100000
int main(int argc, char **args) {
uint64_t results[N];
int n;
for (n = 0; n < N; ++n) {
results[n] = rdtsc();
}
printf("%s\t%s\n", "Absolute time", "Delta");
for (n = 1; n < N; ++n) {
printf("%lld\t%lld\n", results[n], results[n] - results[n-1]);
}
return 0;
}
#include <time.h>
#include <stdio.h>
#include <stdint.h>
#include "rdtsc.h"
/* Compiled & executed with:
gcc rdtsc_overhead.c -O0 -lrt -o rdtsc_overhead
./rdtsc_overhead 1000000 > rdtsc_overhead_results
Windows:
cl -Od rdtsc_overhead.c
rdtsc_overhead.exe 1000000 > windows_rdtsc_overhead_results
*/
int main(int argc, char **args) {
uint64_t tstart, tend, dummy;
int n, N;
N = atoi(args[1]);
tstart = rdtsc();
for (n = 0; n < N; ++n) {
dummy = rdtsc();
dummy = rdtsc();
dummy = rdtsc();
dummy = rdtsc();
dummy = rdtsc();
dummy = rdtsc();
dummy = rdtsc();
dummy = rdtsc();
dummy = rdtsc();
dummy = rdtsc();
}
tend = rdtsc();
printf("%G\n", (double)(tend - tstart)/N/10);
return 0;
}
#include <stdio.h>
#include <stdint.h>
#include <Windows.h>
/* Compiled & executed with:
cl -Od qtct_delta.c
qtct_delta.exe > windows_qtct_delta_results
*/
#define N 100000
int main(int argc, char **args) {
uint64_t ticks, results[N];
int n;
for (n = 0; n < N; ++n) {
QueryThreadCycleTime(GetCurrentThread(), &ticks);
results[n] = ticks;
}
printf("%s\t%s\n", "Absolute time", "Delta");
for (n = 1; n < N; ++n) {
printf("%lld\t%lld\n", results[n], results[n] - results[n-1]);
}
return 0;
}
#include <stdio.h>
#include <stdint.h>
#include <Windows.h>
/* Compiled & executed with:
cl -Od qtct_overhead.c
qtct_overhead.exe 1000000
*/
int main(int argc, char **args) {
uint64_t tstart, tend, ticks;
int n, N;
N = atoi(args[1]);
QueryThreadCycleTime(GetCurrentThread(), &tstart);
for (n = 0; n < N; ++n) {
QueryThreadCycleTime(GetCurrentThread(), &ticks);
QueryThreadCycleTime(GetCurrentThread(), &ticks);
QueryThreadCycleTime(GetCurrentThread(), &ticks);
QueryThreadCycleTime(GetCurrentThread(), &ticks);
QueryThreadCycleTime(GetCurrentThread(), &ticks);
QueryThreadCycleTime(GetCurrentThread(), &ticks);
QueryThreadCycleTime(GetCurrentThread(), &ticks);
QueryThreadCycleTime(GetCurrentThread(), &ticks);
QueryThreadCycleTime(GetCurrentThread(), &ticks);
QueryThreadCycleTime(GetCurrentThread(), &ticks);
}
QueryThreadCycleTime(GetCurrentThread(), &tend);
printf("%G\n", (double)(tend - tstart)/N/10);
return 0;
}