• 程序高性能取时间问题


     

    系统调用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 ", 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 ");

        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 ", interval_milliseconds);

        }

        else

        {

            MYLOG_INFO("Time-thread exit now ");

        }

    }

     

    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

  • 相关阅读:
    转贴:CSS伪类与CSS伪元素的区别及由来具体说明
    Docker + Consul 多数据中心模拟
    Spring之事件发布系统
    ExecutorService的submit方法的坑
    固定频率调用接口方案
    叠罗汉III之推箱子
    叠罗汉II
    叠罗汉I
    滑雪
    华为OJ:火车进站
  • 原文地址:https://www.cnblogs.com/aquester/p/10563807.html
Copyright © 2020-2023  润新知