系统调用time底层调用的是gettimeofday,因此只需关注gettimeofday的性能,而且不同Linux上的gettimeofday会存在性能差异。围绕gettimeofday的优势主要基于rdtsc指令,rdtsc和CPU核相关,因此实现时需要处理好多核问题,除非进程和CPU建立亲和关系。

并不是每个应用场景需要高精度的时间,如果精度只要求到秒,则可采用更为直接的方式达到高性能取时间的目的。

引用一专门的时间线程,这个专门的时间线程负责取时间,并提供读取时间接口。时间线程每10ms更新一次时间,即可满足秒级精度需求。

时间线程对时间的读写操作采用原子读写,开销低性能高。看一个基于libmooon的具体实现(实现简单,其它可参照):

1) 头文件

// 提供秒级时间,
// 可用于避免多个线程重复调用time(NULL)
// 注:32位平台上的毫秒级不准
class CTimeThread
{
    SINGLETON_DECLARE(CTimeThread); // 单例方式方便使用
 
public:
    CTimeThread();
    ~CTimeThread();
    int64_t get_seconds() const; // 可用,近乎精度
    int64_t get_milliseconds() const; // 基本可用,但不精度
    void stop(); // 同一对象在stop后不能重复使用
    bool start(uint32_t interval_milliseconds); // 启动时间线程
    void wait(); // 等待时间线程退出
    void run(); // 运行时间线程
 
private:
    CAtomic<bool> _stop;
#if __WORDSIZE==64
    CAtomic<int64_t> _seconds; // 原子值,不用加锁安全读和写
    CAtomic<int64_t> _milliseconds;
#else
    CAtomic<int> _seconds;
    CAtomic<int> _milliseconds;
#endif // __WORDSIZE==64
    uint32_t _interval_milliseconds;
    CThreadEngine* _engine; // moooon提供的线程引擎
};

 

2) 实现文件

SINGLETON_IMPLEMENT(CTimeThread);
 
CTimeThread::CTimeThread()
    : _stop(false),
      _interval_milliseconds(1000),
      _engine(NULL)
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
 
#if __WORDSIZE==64
    _seconds = static_cast<int64_t>(tv.tv_sec);
    _milliseconds = static_cast<int64_t>(tv.tv_usec);
#else
    _seconds = static_cast<int>(tv.tv_sec);
    _milliseconds = static_cast<int>(tv.tv_usec);
#endif // __WORDSIZE==64
}
 
CTimeThread::~CTimeThread()
{
    wait();
}
 
int64_t CTimeThread::get_seconds() const
{
#if __WORDSIZE==64
    return _seconds.operator int64_t(); // 原子取时间
#else
    return _seconds.operator int();
#endif // __WORDSIZE==64
}
 
int64_t CTimeThread::get_milliseconds() const
{
#if __WORDSIZE==64
    return _milliseconds.operator int64_t();
#else
    return _milliseconds.operator int();
#endif // __WORDSIZE==64
}
 
void CTimeThread::stop()
{
    _stop = true;
}
 
bool CTimeThread::start(uint32_t interval_milliseconds)
{
    try
    {
        _interval_milliseconds = interval_milliseconds;
        _engine = new mooon::sys::CThreadEngine(mooon::sys::bind(&CTimeThread::run, this));
        return true;
    }
    catch (sys::CSyscallException& ex)
    {
        MYLOG_ERROR("Start time-thread failed: %s\n", ex.str().c_str());
        return false;
    }
}
 
void CTimeThread::wait()
{
    if (_engine != NULL)
    {
        _engine->join();
        delete _engine;
        _engine = NULL;
    }
}
 
void CTimeThread::run()
{
    struct timeval start_tv, exit_tv;
    gettimeofday(&start_tv, NULL);
 
    MYLOG_INFO("Time-thread start now\n");
    while (!_stop)
    {
        struct timeval tv;
        gettimeofday(&tv, NULL);
 
        const int64_t milliseconds = static_cast<int64_t>(tv.tv_sec*1000 + tv.tv_usec/1000);
#if __WORDSIZE==64
        _seconds = static_cast<int64_t>(tv.tv_sec);
        _milliseconds = milliseconds; // 原子更新
#else
        _seconds = static_cast<int>(tv.tv_sec);
        _milliseconds = static_cast<int>(milliseconds);
#endif // __WORDSIZE==64
 
        // _interval_milliseconds越小精度越高,
        // 一般秒级精度,值为10ms即足够,甚至100ms也够用了。
        CUtils::millisleep(_interval_milliseconds);
    }
 
    gettimeofday(&exit_tv, NULL);
    if (start_tv.tv_sec<=exit_tv.tv_sec ||
        start_tv.tv_usec<=exit_tv.tv_usec)
    {
        const uint64_t interval_milliseconds = (exit_tv.tv_sec-start_tv.tv_sec)*1000 + (exit_tv.tv_usec-start_tv.tv_usec)/1000;
        MYLOG_INFO("Time-thread exit now: %" PRIu64"ms\n", interval_milliseconds);
    }
    else
    {
        MYLOG_INFO("Time-thread exit now\n");
    }
}

 

3) 使用示例:

#include <mooon/sys/time_thread.h>
 
// 启动时间线程
mooon::sys::CTimeThread::get_singleton()->start(10);
// 取得当前时间(单位:秒)
_now = mooon::sys::CTimeThread::get_singleton()->get_seconds();

 

64位的Linux实现了vsyscall,基于vsyscall实现的gettimeofday性能已达每秒千万级别。vsyscall有局限性,只允许4个系统调用,只能分配较小的内存。VDSO(Virtual Dynamic Shared Object)和vsyscall相同,允许应用在用户空间(不经过内核)执行一些内核操作,但可提供超过4个系统调用,VDSO是glibc提供的功能。

要使VDSO生效,执行:

echo 1 > /proc/sys/kernel/vsyscall64