蓝天

程序高性能取时间问题

 

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

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

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

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

1) 头文件(https://github.com/eyjian/libmooon/blob/master/include/mooon/sys/time_thread.h

// 提供秒级时间,

// 可用于避免多个线程重复调用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) 实现文件(https://github.com/eyjian/libmooon/blob/master/src/sys/time_thread.cpp

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个系统调用,只能分配较小的内存。VDSOVirtual Dynamic Shared Object)和vsyscall相同,允许应用在用户空间(不经过内核)执行一些内核操作,但可提供超过4个系统调用,VDSOglibc提供的功能。

要使VDSO生效,执行:

echo 1 > /proc/sys/kernel/vsyscall64

 

参考:

1) https://alittleresearcher.blogspot.com/2017/04/linux-vdso-and-vsyscall-history.html

2) https://stackoverflow.com/questions/19938324/what-are-vdso-and-vsyscall

3) https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_MRG/1.3/html/Realtime_Tuning_Guide/sect-Realtime_Tuning_Guide-General_System_Tuning-gettimeofday_speedup.html

posted on 2019-03-20 11:24  #蓝天  阅读(874)  评论(0编辑  收藏  举报

导航