使用时间戳计数器获取时间戳

2024-03-17

我使用下面的代码来获取处理器的时钟周期

unsigned long long rdtsc(void)
{
  unsigned hi, lo;
  __asm__ __volatile__ ("rdtsc" : "=a"(lo), "=d"(hi));
  return ( (unsigned long long)lo)|( ((unsigned long long)hi)<<32 );
}

我得到一些值,比如 43,但是这里的单位是什么?是微秒还是纳秒。

我使用下面的代码来获取我的主板的频率。

cat /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq
1700000

我还使用下面的代码来查找我的处理器速度

dmidecode -t processor | grep "Speed"
Max Speed: 3700 MHz
Current Speed: 3700 MHz

现在我如何使用上述频率并将其转换为微秒或毫秒?


对上述问题的简单回答,“如何将 TSC 频率转换为微秒或毫秒?”是:你没有。 TSC(时间戳计数器)时钟频率的实际值取决于硬件,并且在某些硬件上可能会在运行时发生变化。要实时测量,您可以使用clock_gettime(CLOCK_REALTIME) or clock_gettime(CLOCK_MONOTONIC)在Linux中。

正如 Peter Cordes 在评论(2018 年 8 月)中提到的,在大多数当前的 x86-64 架构上,时间戳计数器(通过 RDTSC 指令访问)__rdtsc()函数声明于<x86intrin.h>)计算参考时钟周期,而不是 CPU 时钟周期。他的回答C++中的类似问题 https://stackoverflow.com/a/51907627/1475978对于 x86-64 上的 Linux 中的 C 也有效,因为编译器在编译 C 或 C++ 时提供底层内置,而答案的其余部分涉及硬件详细信息。我也建议您阅读那一篇。

这个答案的其余部分假设根本问题是微基准测试代码,以找出某个函数的两个实现如何相互比较。


在 x86(Intel 32 位)和 x86-64(AMD64、Intel 和 AMD 64 位)架构上,您可以使用__rdtsc() from <x86intrin.h>找出经过的 TSC 时钟周期数。这可用于测量和比较某些功能的不同实现所使用的周期数,通常是很多次。

请注意,TSC 时钟与 CPU 时钟的关系存在硬件差异。上述最近的答案对此进行了一些详细介绍。出于 Linux 中的实际目的,在 Linux 中使用就足够了cpufreq-set禁用频率缩放(以确保 CPU 和 TSC 频率之间的关系在微基准测试期间不会改变),并且可以选择taskset将微基准测试限制为特定的 CPU 核心。这确保了在该微基准测试中收集的结果产生可以相互比较的结果。

(正如 Peter Cordes 评论的那样,我们还想添加_mm_lfence() from <emmintrin.h>(包括由<immintrin.h>)。这可确保 CPU 不会在内部对 RDTSC 操作与要进行基准测试的函数进行重新排序。您可以使用-DNO_LFENCE如果需要,可以在编译时忽略这些。)

假设你有函数void foo(void); and void bar(void);您想要比较的:

#include <stdlib.h>
#include <x86intrin.h>
#include <stdio.h>

#ifdef    NO_LFENCE
#define   lfence()
#else
#include <emmintrin.h>
#define   lfence()  _mm_lfence()
#endif

static int cmp_ull(const void *aptr, const void *bptr)
{
    const unsigned long long  a = *(const unsigned long long *)aptr;
    const unsigned long long  b = *(const unsigned long long *)bptr;
    return (a < b) ? -1 :
           (a > b) ? +1 : 0;
}

unsigned long long *measure_cycles(size_t count, void (*func)())
{
    unsigned long long  *elapsed, started, finished;
    size_t               i;

    elapsed = malloc((count + 2) * sizeof elapsed[0]);
    if (!elapsed)
        return NULL;

    /* Call func() count times, measuring the TSC cycles for each call. */
    for (i = 0; i < count; i++) {
        /* First, let's ensure our CPU executes everything thus far. */
        lfence();
        /* Start timing. */
        started = __rdtsc();
        /* Ensure timing starts before we call the function. */
        lfence();
        /* Call the function. */
        func();
        /* Ensure everything has been executed thus far. */
        lfence();
        /* Stop timing. */
        finished = __rdtsc();
        /* Ensure we have the counter value before proceeding. */
        lfence();

        elapsed[i] = finished - started;
    }

    /* The very first call is likely the cold-cache case,
       so in case that measurement might contain useful
       information, we put it at the end of the array.
       We also terminate the array with a zero. */
    elapsed[count] = elapsed[0];
    elapsed[count + 1] = 0;

    /* Sort the cycle counts. */
    qsort(elapsed, count, sizeof elapsed[0], cmp_ull);

    /* This function returns all cycle counts, in sorted order,
       although the median, elapsed[count/2], is the one
       I personally use. */
    return elapsed;
}

void benchmark(const size_t count)
{
    unsigned long long  *foo_cycles, *bar_cycles;

    if (count < 1)
        return;

    printf("Measuring run time in Time Stamp Counter cycles:\n");
    fflush(stdout);

    foo_cycles = measure_cycles(count, foo);
    bar_cycles = measure_cycles(count, bar);

    printf("foo(): %llu cycles (median of %zu calls)\n", foo_cycles[count/2], count);
    printf("bar(): %llu cycles (median of %zu calls)\n", bar_cycles[count/2], count);

    free(bar_cycles);
    free(foo_cycles);
}

请注意,上述结果非常特定于所使用的编译器和编译器选项,当然还取决于运行它的硬件。周期中位数可以解释为“所采用的 TSC 周期的典型数量”,因为测量结果并不完全可靠(可能会受到进程外部事件的影响;例如,通过上下文切换或迁移到另一个核心)一些CPU)。出于同样的原因,我不相信最小值、最大值或平均值。

然而,这两个实现'(foo() and bar()) 以上循环计数can在微基准测试中进行比较,以了解它们的性能如何相互比较。请记住,微基准测试结果可能不会扩展到实际工作任务,因为任务的资源使用交互非常复杂。一个函数可能在所有微基准测试中都表现出色,但在现实世界中却比其他函数差,因为它只有在有大量 CPU 缓存可供使用时才有效。

 

一般来说,在 Linux 中,您可以使用CLOCK_REALTIME使用时钟来测量实时时间(挂钟时间),其方式与上述相同。CLOCK_MONOTONIC甚至更好,因为它不受管理员可能对实时时钟进行的直接更改的影响(例如,如果他们注意到系统时钟提前或落后);仅应用由于 NTP 等引起的漂移调整。夏令时或其变化不会影响使用任一时钟的测量。同样,多次测量的中位数是我寻求的结果,因为测量代码本身之外的事件可能会影响结果。

例如:

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

#ifdef   NO_LFENCE
#define  lfence()
#else
#include <emmintrin.h>
#define  lfence() _mm_lfence()
#endif

static int cmp_double(const void *aptr, const void *bptr)
{
    const double a = *(const double *)aptr;
    const double b = *(const double *)bptr;
    return (a < b) ? -1 :
           (a > b) ? +1 : 0;
}

double median_seconds(const size_t count, void (*func)())
{
    struct timespec started, stopped;
    double         *seconds, median;
    size_t          i;

    seconds = malloc(count * sizeof seconds[0]);
    if (!seconds)
        return -1.0;

    for (i = 0; i < count; i++) {
        lfence();
        clock_gettime(CLOCK_MONOTONIC, &started);
        lfence();
        func();
        lfence();
        clock_gettime(CLOCK_MONOTONIC, &stopped);
        lfence();
        seconds[i] = (double)(stopped.tv_sec - started.tv_sec)
                   + (double)(stopped.tv_nsec - started.tv_nsec) / 1000000000.0;
    }

    qsort(seconds, count, sizeof seconds[0], cmp_double);
    median = seconds[count / 2];
    free(seconds);
    return median;
}

static double realtime_precision(void)
{
    struct timespec t;

    if (clock_getres(CLOCK_REALTIME, &t) == 0)
        return (double)t.tv_sec
             + (double)t.tv_nsec / 1000000000.0;

    return 0.0;
}

void benchmark(const size_t count)
{
    double median_foo, median_bar;
    if (count < 1)
        return;

    printf("Median wall clock times over %zu calls:\n", count);
    fflush(stdout);

    median_foo = median_seconds(count, foo);
    median_bar = median_seconds(count, bar);

    printf("foo(): %.3f ns\n", median_foo * 1000000000.0);
    printf("bar(): %.3f ns\n", median_bar * 1000000000.0);

    printf("(Measurement unit is approximately %.3f ns)\n", 1000000000.0 * realtime_precision());
    fflush(stdout);
}

 

一般来说,我个人更喜欢在单独的单元中编译基准测试函数(到单独的目标文件),并对不执行任何操作的函数进行基准测试以估计函数调用开销(尽管它往往会高估开销;即产生太大的开销估计,因为一些函数调用开销是延迟而不是实际花费的时间,并且在实际功能的这些延迟期间可以进行一些操作)。

重要的是要记住,上述测量值只能用作指示,因为在现实世界的应用程序中,诸如缓存局部性之类的东西(特别是在当前的机器上,具有多级缓存和大量内存)会极大地影响不同的实现。

例如,您可以比较快速排序和基数排序的速度。根据键的大小,基数排序需要相当大的额外数组(并使用大量缓存)。如果使用排序例程的实际应用程序不会同时使用大量其他内存(因此排序的数据基本上是缓存的数据),那么如果有足够的数据(并且实现是合理的),则基数排序会更快)。但是,如果应用程序是多线程的,并且其他线程混洗(复制或传输)大量内存,则使用大量缓存的基数排序将逐出也缓存的其他数据;即使基数排序函数本身没有表现出任何严重的减速,它可能会减慢其他线程的速度,从而减慢整个程序的速度,因为其他线程必须等待它们的数据被重新缓存。

这意味着您应该信任的唯一“基准”是实际硬件上使用的挂钟测量值,使用实际工作数据运行实际工作任务。其他一切都受到许多条件的影响,并且或多或少令人怀疑:有迹象表明,是的,但不是很可靠。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用时间戳计数器获取时间戳 的相关文章

  • 分配内存空间的宏

    我需要让一个汇编程序员来计算帕斯卡三角形 https en wikipedia org wiki Pascal 27s triangle 这样帕斯卡三角形的每一行都与其他行分开存储在内存中 我想做一个 但我不知道如何使用宏在汇编中做到这一点
  • g++ 内联汇编括号中不匹配

    g 向我抱怨以下代码中缺少括号 1 2 3 v v v asm volatile inb 1 0 a result Nd portnumber 1 2 3 正如您所看到的 括号是匹配
  • WPF C# - 计时器倒计时

    如何在用 WPF C 编写的代码中实现以下内容 我有一个 ElementFlow 控件 在其中实现了 SelectionChanged 事件 该事件 根据定义 在控件的项目选择发生更改时触发特定事件 我想要它做的是 启动计时器 如果计时器达
  • 如何禁用浮点单元(FPU)?

    我想在 x86 系统中禁用 FPU MMX SSE 指令 并且我将为设备不可用异常实现一个处理程序 我已经提到过控制寄存器 wiki 页面 http en wikipedia org wiki Control register 看来我必须在
  • 互动倒计时增加?

    我有一个表单 如果没有完成任何鼠标交互 我想在 5 秒后关闭它 但如果完成任何鼠标交互 我希望它关闭countdown 5 seconds每次交互都会增加 5 秒 这是我到目前为止想到的 int countdown 5 System Tim
  • gcc 如何知道内联汇编中使用的寄存器大小?

    我有内联汇编代码 define read msr index buf asm volatile rdmsr d buf 1 a buf 0 c index 使用该宏的代码 u32 buf 2 read msr 0x173 buf 我发现反汇
  • dmesg 和 /var/log/kern.log 之间的区别

    我正在修改kvm模块 并在内核代码中添加了printk语句 运行虚拟机后 printk为我提供了错误地址和有关客户操作系统的其他信息 我需要根据此信息生成统计信息 当我使用 dmesg 时 我只能看到错误地址 在内核空间中 即它们的地址高于
  • 为什么当设置为 TLS 选择器时,ES 和 DS 在 64 位内核上最终会归零?

    下面的 32 位程序调用set thread area 2 http linux die net man 2 set thread area在 GDT 中创建一个条目 该条目旨在用于 TLS 通常将结果选择器放入FS or GS并成功使用
  • 如何编写需要内核源头文件的 BitBake 驱动程序配方?

    介绍 我有一个do install我为驱动程序编写的 BitBake 配方中的任务 我在其中执行自定义install脚本 任务失败 因为安装脚本无法在其中找到内核源头文件
  • 预取双类成员需要转换为 char*?

    我有一个正在使用的课程 mm prefetch 预先请求包含 double 类型的类成员的缓存行 class MyClass double getDouble return dbl other members double dbl othe
  • 使用Linux虚拟鼠标驱动

    我正在尝试实施一个虚拟鼠标驱动程序根据基本 Linux 设备驱动程序书 有一个用户空间应用程序 它生成坐标以及内核模块 See 虚拟鼠标驱动程序和用户空间应用程序代码 http www embeddedlinux org cn Essent
  • 如何让c代码执行hex机器代码?

    我想要一个简单的 C 方法能够在 Linux 64 位机器上运行十六进制字节码 这是我的 C 程序 char code x48 x31 xc0 include
  • 将两个 32 位整数向量相乘,生成 32 位结果元素向量

    将每个 32 位条目乘以 2 的最佳方法是什么 mm256i互相注册 mm256 mul epu32不是我正在寻找的 因为它产生 64 位输出 我想要每个 32 位输入元素都有一个 32 位结果 而且 我确信两个 32 位值的乘法不会溢出
  • 在 Android 中的计时器内运行异步任务

    我正在开发一个基本的聊天类型应用程序 目前我正在运行代码 如下所示 class GetMsgs extends AsyncTask
  • Android:如何在触摸事件中手动实现长按?

    简短版本 我想要一种方法来在 onTouchEvent 上启动基于时间的计数器 并测试在响应之前是否已经过了一定的时间 作为手动 LongTouch 检测 解释 我有一个自定义 imageView 可以通过两根手指滑动滑入 滑出屏幕 我想向
  • 在 x86 ASM 中测试零通常哪个更快:“TEST EAX, EAX”与“TEST AL, AL”?

    测试 AL 中的字节是否为零 非零通常哪个更快 TEST EAX EAX TEST AL AL 假设之前有一个 MOVZX EAX BYTE PTR ESP 4 指令加载了一个带有零扩展的字节参数到 EAX 的其余部分 防止了我已经知道的组
  • 如何知道寄存器是否是“通用寄存器”?

    我试图了解寄存器必须具备什么标准才能被称为 通用寄存器 我相信通用寄存器是一个可以用于任何用途的寄存器 用于计算 将数据移入 移出等 并且是一个没有特殊用途的寄存器 现在我读到了ESP寄存器是通用寄存器 我猜是ESP寄存器可以用于任何事情
  • 在中断时获取 current->pid

    我正在Linux调度程序上写一些东西 我需要知道在我的中断到来之前哪个进程正在运行 当前的结构可用吗 如果我在中断处理程序中执行 current gt pid 我是否可以获得我中断的进程的 pid 你可以 current gt pid存在并
  • 从内核空间中的块设备读取

    我正在编写一个内核模块 需要从现有的块设备执行读取 dev 东西 有谁知道有任何其他模块可以执行这些操作 我可以用作参考吗 欢迎任何指点 Linux 2 6 30 如果你真的绝对必须那么使用filp open filp close vfs
  • 如何在 Linux x86_64 上模拟 iret

    我正在编写一个基于 Intel VT 的调试器 由于当 NMI Exiting 1 时 iret 指令在 vmx guest 中的性能发生了变化 所以我应该自己处理vmx主机中的NMI 否则 guest会出现nmi可重入错误 我查了英特尔手

随机推荐