144. rdtsc

rdtsc是x86的时时间戳计数器,在ARM上是没有这个寄存器的,所以需要把它替换为相应的实现。

在C/C++源文件当中,嵌入了汇编代码,使用rdtsc(X86特有的汇编指令)获取时间戳计数器的值。

#include "util/tc_monitor.h"
#include "util/tc_thread.h"
#include "util/tc_autoptr.h"

#define rdtsc(low,high) \
     __asm__ __volatile__("rdtsc" : "=a" (low), "=d" (high))

#define TNOW     tars::TC_TimeProvider::getInstance()->getNow()
#define TNOWMS   tars::TC_TimeProvider::getInstance()->getNowMs()

我们把它替换掉,使用内联函数即可:

#include "util/tc_monitor.h"
#include "util/tc_thread.h"
#include "util/tc_autoptr.h"

__inline __attribute__((always_inline)) uint64_t rdtsc() {
#if defined(__i386__)
    int64_t ret;
    __asm__ volatile ("rdtsc" : "=A" (ret) );
    return ret;
#elif defined(__x86_64__) || defined(__amd64__)
    uint32_t lo, hi;
    __asm__ __volatile__("rdtsc" : "=a" (lo), "=d" (hi));
    return (((uint64_t)hi << 32) | lo);
#elif defined(__aarch64__)
uint64_t cntvct;
    asm volatile ("isb; mrs %0, cntvct_el0; isb; " : "=r" (cntvct) :: "memory");
    return cntvct;
#else
#warning No high-precision counter available for your OS/arch
    return 0;
#endif
}

#define TNOW     tars::TC_TimeProvider::getInstance()->getNow()
#define TNOWMS   tars::TC_TimeProvider::getInstance()->getNowMs()

原调用方法

void TC_TimeProvider::setTsc(timeval& tt)
{
    uint32_t low    = 0;
    uint32_t high   = 0;
    rdtsc(low,high);
    uint64_t current_tsc    = ((uint64_t)high << 32) | low;

    uint64_t& last_tsc      = _tsc[!_buf_idx];
    timeval& last_tt        = _t[_buf_idx];
    //.....
}

修改为新调用方法:

void TC_TimeProvider::setTsc(timeval& tt)
{
    uint64_t current_tsc    = rdtsc();

    uint64_t& last_tsc      = _tsc[!_buf_idx];
    timeval& last_tt        = _t[_buf_idx];
    //....
}

144.1. linux 获取时间的办法:

man 3 clock_gettime

int clock_gettime(clockid_t clk_id, struct timespec *tp);

clk_id可以是以下的值

  • CLOCK_REALTIME 系统实时时间,随系统实时时间改变而改变
  • CLOCK_REALTIME_COARSE 低精度,更快的CLOCK_REALTIME版本
  • CLOCK_MONOTONIC 系统启动到现在的技术,不受系统用户时间跳跃的影响,但是受adjtime()和ntp影响
  • CLOCK_MONOTONIC_COARSE 低精度,更快的CLOCK_MONOTONIC版本
  • CLOCK_MONOTONIC_RAW 和CLOCK_MONOTONIC类似,但是提供基于硬件的时间,不受ntp影响
  • CLOCK_BOOTTIME 和CLOCK_MONOTONIC一样,除了记录系统休眠时间。
  • CLOCK_PROCESS_CPUTIME_ID,本进程到当前代码系统CPU花费的时间
  • CLOCK_THREAD_CPUTIME_ID,本线程到当前代码系统CPU花费的时间

示例程序:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>

int main()
{
        int i;
        struct timespec t1 = {0, 0};
        struct timespec t2 = {0, 0};

        for(i=0; i<5; i++)
        {
                if ( clock_gettime(CLOCK_REALTIME, &t1) == -1)
                {
                        perror("clock gettime");
                        exit(EXIT_FAILURE);
                }

                sleep(1);

                if ( clock_gettime(CLOCK_REALTIME, &t2) == -1)
                {
                        perror("clock gettime");
                        exit(EXIT_FAILURE);
                }
                printf("time pass:%ld ms\n", (t2.tv_sec-t1.tv_sec)*1000+
                                (t2.tv_nsec-t1.tv_nsec)/1000000);
                //t2.tv_nsec-t1.nsec是有可能是负数的
        }
}

144.2. 确认机器支持哪些时钟寄存器

cat /proc/cpuinfo | grep -i tsc
flags : ... tsc  rdtscp constant_tsc nonstop_tsc ...

RDTSC没有保序的功能,所以会导致想测的指令在RDTSC区间之外进行。这样为避免CPU乱序,需要用cpuid保序,之后的CPU都有RDTSCP ,这是已经保序的指令,所以只要有这个指令应该使用这个,而不是老版的 # 获取时间戳本延时测试

在我的x86服务器上:

ClockBench.cpp
                   Method       samples     min     max     avg  median   stdev
           CLOCK_REALTIME       1023      21.00   25.00   22.37   23.00    0.88
    CLOCK_REALTIME_COARSE       1023       0.00    0.00    0.00    0.00    0.00
          CLOCK_MONOTONIC       1023      21.00 2173.00   24.37 1097.00   67.33
      CLOCK_MONOTONIC_RAW       1023     385.00  415.00  388.77  400.00    5.80
   CLOCK_MONOTONIC_COARSE       1023       0.00    0.00    0.00    0.00    0.00
              cpuid+rdtsc       1023     112.00  136.00  113.02  124.00    1.88
                   rdtscp       1023      32.00   32.00   32.00   32.00    0.00
                    rdtsc       1023      24.00   28.00   24.50   26.00    1.32
Using CPU frequency = 1.000000