程序高性能取时间问题
系统调用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个系统调用,只能分配较小的内存。VDSO(Virtual Dynamic Shared Object)和vsyscall相同,允许应用在用户空间(不经过内核)执行一些内核操作,但可提供超过4个系统调用,VDSO是glibc提供的功能。
要使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