linux系统 时钟源 起始值与步长增加两种类型

linux系统 时钟源 起始值与步长增加两种类型Linux内核中有两种不同的clock设备,一种是clocksource设备,另一种是clockevent设备。Clocksource设备一般是一个根据固定频率不停增加的计数器,如STC,内核利用该设备可以计算出从系统启动到当前所经过的时间,再加上RTC所提供的初始时间就能得到当前时间(墙上时间)。Clockevent设备则用来提供中断,Clockevent设备可以配置为按固定周期发生中…

大家好,欢迎来到IT知识分享网。

Linux内核中有两种不同的clock设备,一种是clock source设备,另一种是clock event设备。Clock source设备一般是一个根据固定频率不停增加的计数器,如STC,内核利用该设备可以计算出从系统启动到当前所经过的时间,再加上RTC所提供的初始时间就能得到当前时间(墙上时间)。Clock event设备则用来提供中断,Clock event设备可以配置为按固定周期发生中断(periodic)或者产生一个特定时间间隔后的中断(onshot)
————————————————
版权声明:本文为CSDN博主「colddown」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/colddown/article/details/38878909

 

RaymondSQ

让数据动起来

gettimeofday、clockgettime 以及不同时钟源的影响

我们写程序的时候经常会使用计时函数,比如RPC中计算超时时间,日志中打印当前时间,性能profile中计算某个函数的执行时间等。在使用时间函数的时候,我们一般默认认为时间函数本身的性能是很高的,对主逻辑的影响可以忽略不计。虽然绝大部分情况下这个假设都没问题,但是了解更多细节能够增加我们对问题的把控力,利于系统的设计和问题的调查。

首先来比较gettimeofday/clock_gettime的性能。

程序代码见后

 

Glibc版本:

$rpm -qa|grep glibc-comm

glibc-common-2.5-81

内核版本:

$uname -a

2.6.32-220.23.2

 

$./a.out -help

 [gettimeofday/clock_gettime] thread_number loop_count

 

$./a.out gettimeofday 1 100000000

gettimeofday(50035480681901) , times : 100000000

thread 1105828160 consume 4000225 us

单线程gettimeofday大概每次40ns

图1看出,gettimeofday走的是vsyscall[1](虚拟系统粗糙的描述就是不经过内核进程的切换就可以调用一段预定好的内核代码),没有线程切换的开销。

图1 gettimeofday 走vsyscall

linux系统 时钟源 起始值与步长增加两种类型

图2 gettimeofday能将usr态cpu消耗压到100%

linux系统 时钟源 起始值与步长增加两种类型

因为走vsyscall,没有线程切换,所以多线程运行性能跟单线程是类似的。

$./a.out gettimeofday 12 100000000

gettimeofday(51127820371298) , times : 100000000

thread 1201568064 consume 4111854 us

 

$./a.out clock_gettime 1 100000000

clock_gettime(50265567600696623) , times : 100000000

thread 1107867968 consume 10242448 us

单线程clock_gettime大概每次100ns

图3 clock_gettime 走真正的系统调用

linux系统 时钟源 起始值与步长增加两种类型

图4 clock_gettime 70%的cpu花在sys态,确实进入了系统调用流程

linux系统 时钟源 起始值与步长增加两种类型

因为开销集中在系统调用本身,而不是花在进程切换上,所以多线程结果跟单线程类似。

$./a.out clock_gettime 12 100000000

clock_gettime(50369061997211567) , times : 100000000

thread 1122031936 consume 10226828 us

这里说“开销集中在系统调用本身”意思是说clock_gettime本身的执行就非常耗费时间,其大概的调用路径是

clock_gettime -> sys_call -> sys_clock_gettime -> getnstimeofday -> read_tsc -> native_read_tsc

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

void getnstimeofday(struct timespec *ts)

{

    unsigned long seq;

    s64 nsecs;

    WARN_ON(timekeeping_suspended);

do {

    // 下面代码执行过程中xtime可能会被更改,这里通过持有一个序号来避免显示加锁,如果该代码执行完毕之后,seq并未改变,说明xtime未被更改,此次执行成功,否则重试;无论这里重试与否,CPU都会一直干活;

    seq = read_seqbegin(&xtime_lock);

        *ts = xtime;

        nsecs = timekeeping_get_ns(); //从当前时钟源取更细致的时间精度

        /* If arch requires, add in gettimeoffset() */

        nsecs += arch_gettimeoffset(); // 中断从发出到执行有时间消耗,可能需要做补偿

    while (read_seqretry(&xtime_lock, seq));

    timespec_add_ns(ts, nsecs); //时间转换

}

/* Timekeeper helper functions. */

static inline s64 timekeeping_get_ns(void)

{

    cycle_t cycle_now, cycle_delta;

    struct clocksource *clock;

    /* read clocksource: */

clock = timekeeper.clock;

// 使用系统注册的时钟源[2]来读取,当前情况下,一般tsc是默认时钟源

// /sys/devices/system/clocksource/clocksource0/current_clocksource

// 下面的调用最终执行native_read_tsc, 里面就是一条汇编指令rdtsc

    cycle_now = clock->read(clock);

    /* calculate the delta since the last update_wall_time: */

    cycle_delta = (cycle_now - clock->cycle_last) & clock->mask;

    /* return delta convert to nanoseconds using ntp adjusted mult. */

    return clocksource_cyc2ns(cycle_delta, timekeeper.mult,

                  timekeeper.shift);

}

  

上面说到时钟源是可以替换的,

$cat /sys/devices/system/clocksource/clocksource0/available_clocksource

tsc hpet acpi_pm

这是所有可用的时钟源。硬件出问题或者内核bug有时候会使得tsc不可用,于是时钟源默认会切换到hpet,使用hpet后gettimeofday和clock_gettime性能如何?测试一下。

$sudo bash -c “echo hpet > /sys/devices/system/clocksource/clocksource0/current_clocksource”

$cat /sys/devices/system/clocksource/clocksource0/current_clocksource

hpet

 

$./a.out gettimeofday 1 100000000

gettimeofday(50067118117357) , times : 100000000

thread 1091926336 consume 71748597 us

延时是原来的17倍,大概700ns一次;clock_gettime 与此类似,因为此时瓶颈已经不是系统调用,而是hpet_read很慢。

此时大概的调用路径是

clock_gettime -> sys_call -> sys_clock_gettime -> getnstimeofday -> read_hpet -> hpet_readl –> readl

实现非常直白,但是readl是读时钟设备的内存映射,慢是肯定的了。

 

总结来说,上文制定的内核和glibc版本下,tsc时钟源,gettimeofday 比 clock_gettime快1倍多,适合做计时用(clock_gettime使用CLOCK_REALTIME_COARSE也是很快的);如果因为tsc不稳定(硬件或者内核bug都可能导致,碰到过),hpet一般不会同时出问题,这时hpet成为了新的时钟源,整体性能下降数十倍,两者没啥区别了。

 

[1].     On vsyscalls and the vDSO : http://lwn.net/Articles/446528/

[2].     Linux内核的时钟中断机制 : http://wenku.baidu.com/view/4a9f37f24693daef5ef73d32.html

 

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

#include <sys/time.h>

#include <iostream>

#include <time.h>

 

using namespace std;

 

uint64_t now()

{

    struct timeval tv;

    gettimeofday(&tv, NULL);

    return tv.tv_sec * 1000000 + tv.tv_usec;

}

 

void* func_gettimeofday(void* p)

{

    int32_t c = *(int32_t*)p;

    uint64_t start = now();

 

    uint64_t us = 0;

    int i = 0;

    while (i++ < c) {

        struct timeval tv;

        gettimeofday(&tv, NULL);

        us += tv.tv_usec; // avoid optimize

    }

    cout << "gettimeofday(" << us << ") , times : " << c << endl;

    cout << "thread " << pthread_self() << " consume " << now() - start << " us" << endl;

    return 0;

}

 

void* func_clockgettime(void* p)

{

    int32_t c = *(int32_t*)p;

    uint64_t start = now();

 

    uint64_t us = 0;

    int i = 0;

    while (i++ < c) {

        struct timespec tp;

        clock_gettime(CLOCK_REALTIME, &tp);

        us += tp.tv_nsec;

    }

    cout << "clock_gettime(" << us << ") , times : " << c << endl;

    cout << "thread " << pthread_self() << " consume " << now() - start << " us" << endl;

    return 0;

}

 

int main(int argc, char** argv)

{

    if (argc != 4) {

        cout << " [gettimeofday/clock_gettime] thread_number loop_count" << endl;

        exit(-1);

    }

    string mode = string(argv[1]);

    int n = atoi(argv[2]);

    int loop = atoi(argv[3]);

    pthread_t* ts = new pthread_t[n];

    for (int i = 0; i < n; i++) {

        if (mode == "gettimeofday") {

            pthread_create(ts+i, NULL, func_gettimeofday, &loop);

        else {

            pthread_create(ts+i, NULL, func_clockgettime, &loop);

        }

    }

 

    for (int i = 0; i < n; i++) {

        pthread_join(ts[i], NULL);

    }

 

    delete [] ts;

 

    return 0;

}

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/23101.html

(0)
上一篇 2023-12-15 15:33
下一篇 2023-12-15 20:15

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信