【问题标题】:Fastest way to get a timestamp获取时间戳的最快方法
【发布时间】:2018-07-14 12:46:54
【问题描述】:

我正在实现一些数据结构,我需要在一段时间后使某些条目无效,因此对于每个条目,我需要维护其插入时间戳。当我得到一个条目时,我需要再次获取时间戳并计算插入所经过的时间(如果它太旧,我不能使用它)。

这个数据结构被许多线程高度满足,所以我必须以最有效的方式获取这个时间戳(在insertfind)。效率在这里非常重要。

如果重要的话,我正在使用 linux 机器,使用 C++ 进行开发。 检索时间戳最有效的方法是什么?

顺便说一句,在我从事的一些旧项目中,我记得我看到了一些直接从 CPU 获取时间戳的汇编命令(不记得该命令)。

【问题讨论】:

  • linux 的gettimeofday 是高度优化的,只是因为它经常被普通软件调用(例如在数据库中的“回滚”功能中,多个客户端尝试写入数据库的同一部分,你需要知道谁是第一个)。
  • 当然,如果您只想在“不是永远”之后使条目无效,您可以为每个缓存条目增加一个计数器,如果它超过 10、100、1000 或 4711 “其他缓存条目计数”旧,您将其无效。如果您确定 1 分钟前的条目就可以,这当然不是理想的,但必须重新加载超过 5 分钟的条目。
  • 如果std::chrono::system_clock::now() 的速度不够快,我会感到非常惊讶。
  • @user202729 time() 只有 1 秒的分辨率。对很多事情都很好,但对许多用例来说非常不准确。无论如何,我不明白为什么在这个时代不使用std::chrono
  • @michael 然后尝试基准测试。

标签: c++ linux time cpu-registers


【解决方案1】:

我创建了以下基准来测试检索时间戳的几种方法。基准测试是使用带有 -O2 的 GCC 编译的,并在我的 mac 上进行了测试。我测量了每种方法获取 1M 时间戳所需的时间,从结果来看,rdtsc 似乎比其他方法更快。

编辑:基准已修改为支持多线程。

基准代码:

#include <iostream>
#include <chrono>
#include <sys/time.h>
#include <unistd.h>
#include <vector>
#include <thread>
#include <atomic>

#define NUM_SAMPLES 1000000
#define NUM_THREADS 4

static inline unsigned long long getticks(void)
{
    unsigned int lo, hi;

    // RDTSC copies contents of 64-bit TSC into EDX:EAX
    asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
    return (unsigned long long)hi << 32 | lo;
}

std::atomic<bool> g_start(false);
std::atomic<unsigned int> totalTime(0);

template<typename Method>
void measureFunc(Method method)
{
    // warmup
    for (unsigned int i = 0; i < NUM_SAMPLES; i++)
    {   
        method();
    }

    auto start = std::chrono::system_clock::now();

    for (unsigned int i = 0; i < NUM_SAMPLES; i++)
    {   
        method();
    }

    auto end = std::chrono::system_clock::now();
    totalTime += std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
}

template<typename Method>
void measureThread(Method method)
{
    while(!g_start.load());

    measureFunc(method);
}

template<typename Method>
void measure(const std::string& methodName, Method method)
{
    std::vector<std::thread> threads;

    totalTime.store(0);
    g_start.store(false);

    for (unsigned int i = 0; i < NUM_THREADS; i++)
    {
        threads.push_back(std::thread(measureThread<Method>, method));
    }

    g_start.store(true);

    for (std::thread& th : threads)
    {
        th.join();
    }

    double timePerThread = (double)totalTime / (double)NUM_THREADS;

    std::cout << methodName << ": " << timePerThread << "ms per thread" << std::endl;
}

int main(int argc, char** argv)
{
    measure("gettimeofday", [](){ timeval tv; return gettimeofday(&tv, 0); });
    measure("time", [](){ return time(NULL); });
    measure("std chrono system_clock", [](){ return std::chrono::system_clock::now(); });
    measure("std chrono steady_clock", [](){ return std::chrono::steady_clock::now(); });
    measure("clock_gettime monotonic", [](){ timespec tp; return clock_gettime(CLOCK_MONOTONIC, &tp); });
    measure("clock_gettime cpu time", [](){ timespec tp; return clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tp); });
    measure("rdtsc", [](){ return getticks(); });

    return 0;
}

单个线程的结果(以毫秒为单位):

gettimeofday: 54ms per thread
time: 260ms per thread
std chrono system_clock: 62ms per thread
std chrono steady_clock: 60ms per thread
clock_gettime monotonic: 102ms per thread
clock_gettime cpu time: 493ms per thread
rdtsc: 8ms per thread

有 4 个线程:

gettimeofday: 55.25ms per thread
time: 292.5ms per thread
std chrono system_clock: 69.25ms per thread
std chrono steady_clock: 68.5ms per thread
clock_gettime monotonic: 118.25ms per thread
clock_gettime cpu time: 2975.75ms per thread
rdtsc: 10.25ms per thread

从结果来看,std::chrono 从多个线程调用时似乎有一些小的开销,gettimeofday 方法随着线程数量的增加保持稳定。

【讨论】:

  • 您也可以尝试使用steady_clock,它比system_clock 更好地用于缓存到期,并且可以更快,因为它不必转换为绝对时间。
  • 添加了steady_clock,得到的结果与system_clock相似。
  • 使用 rdtsc 的原始输出可能不精确,因为(主要)CPU 中正在进行电源状态切换,并且 x86 CPU 的各种步进/型号/系列可能会返回不同的结果,具体取决于这。另请记住,rdtsc 不是序列化指令,某些指令可能仍在执行或排队等待执行:因此,您可能正在读取时间戳实际执行您想要测量的事物之前.
  • @Daniel Kamil Kozar 你是对的。使用rdtsc 时,您应该了解它的局限性。就我而言,CPU 始终处于全功率状态,准确性并不重要,而性能至关重要。
  • 如果需要,here is an answer 展示了如何将rdtsc 包装在chrono 样式的时钟中以使其更易于使用。这将为您提供与 rdtsc 测量相关的 time_points 和持续时间,而无需额外开销。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-04-30
  • 2013-09-15
  • 1970-01-01
  • 2015-08-20
  • 2013-07-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多