| |
| 1s ==1000ms == 1,000,000us == 1,000,000,000 nanosecond |
| |
| |
| uname -a |
| Linux scott-Z170X 4.15.0-34-generic #37-Ubuntu SMP Mon Aug 27 15:21:48 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux |
| intel i5-6500 |
| |
| 我的CLOCKS_PER_SEC 是100W |
| --------------------------- |
| |
| |
| int64_t %ld , uint64_t %lu |
| longlong %lld , ulonglong %llu |
| |
| |
| double %g |
| float %f |
| int %d |
| uint %u |
| |
| |
| fprintf(stdout, "%g is %g\n", inputValue, outputValue); |
| |
Linux时钟精度:毫秒?微妙?纳秒?
| |
| 首先:linux有一个很重要的概念——节拍,它的单位是(次/秒)。2.6内核这个值是1000,系统中用一个HZ的宏表征这个值。同时有全局的jiffies变量,表征从开机以来经过的节拍次数(这里面还有故事,后面说,先记住这个)。当然还有wall_jiffies的墙上jiffies来表示从 07-01-1970 到现在的节拍数。每个节拍里面执行一次时钟中断。就是说,它的精度是毫秒。 |
| |
| 接着:内核中还有一个变量xtime表征系统的实际时间(墙上时间),定义如下。其中xtime.tv_sec以秒为单位,存放从Unix祖宗定的纪元时间(19700701)到现在的秒数。xtime.tv_nsec以纳秒为单位,记录从上一秒开始经过的纳秒数。就是说,它的精度是纳秒。 |
| |
| struct timespec xtime; |
| |
| struct timespec{ |
| time_t tv_sec; |
| long tv_nsec; |
| }; |
| |
| 最后:linux提供一个gettimeofday的系统调用,它会返回一个timeval的结构体,定义如下。解释同上,tv_sec代表墙上时间的秒,tv_usec表示从上一秒到现在经过的微秒数。就是说,它的精度是微妙。 |
| |
| struct timeval{ |
| long tv_sec; |
| long tv_usec; |
| }; |
| |
| 精彩的来了: |
| 1. 内核中的xtime会在每个时钟中断的时候被更新一次,也就是每个节拍更新一次。你妹!!每毫秒更新一次怎么能冒出来纳秒的精度??而且,内核还有可能丢失节拍。怎么能是纳秒?? |
| 2. 各种书上说,gettimeofday系统调用是读取的xtime的值。日,为啥读出来之后精度丢了?变成微妙了? |
| |
| 寻寻觅觅终于理清了故事: |
| 针对问题1:在linux启动的时候,一个节拍的时间长度还会以纳秒为单位初始化到tick_nsec中,初始化值为999848ns,坑爹啊!不到一毫秒!节拍大约为1000.15Hz。靠!实际的节拍竟然不是准确的1000!所以在每个时钟中断通过wall_jiffies去更新xtime的时候得到的就是一个以纳秒为最小单位的的值。所以!xtime的粒度应该是不到1毫秒,也就是精度是不到1毫秒。 |
| |
| 针对问题2:gettimeday系统调用的读xtime代码部分如下: |
| do{ |
| unsigned long lost; |
| seq = read_seqbegin(&xtime_lock); |
| |
| usec = timer->get_offset(); |
| lost = jiffies - wall_jiffies; |
| if(lost) |
| usec += lost*(1000000/HZ); |
| sec = xtime.tv_sec; |
| usec += (xtime.tv_nsec/1000); |
| |
| }while(read_seqretry(&xtime_lock, seq)) |
| |
| while部分使用了seg锁,只看中间的就好了。加了注释之后就很清晰了。由于节拍可能会丢失,所以lost是丢失的节拍数(不会很多)。至于计时器就比较麻烦了,timer可能有下面四种情况。 |
| |
| a. 如果cur_timer指向timer_hpet对象,该方法使用HPET定时器——Inter与Microsoft开发的高精度定时器频率至少10MHz,也就是说此时可提供真正的微妙级精度。 |
| b. 如果cur_timer指向timer_pmtmr对象,该方法使用ACPI PMT计时器(电源管理定时器)平率大约3.58MHz,也就是说也可以提供真正的微妙级精度。 |
| c. 如果cur_timer指向timer_tsc对象,该方法使用时间戳计数器,内置在所有8086处理器,每个CPU时钟,计数器增加一次,频率就是CPU频率,所以timer精度最高。完全可以胜任微妙级的精度。 |
| d. 如果cur_timer指向timer_pit对象,该方法使用PIT计数器,也即是最开始提到的节拍计数,频率大概是1000Hz,此时显然不能提供精度达到微妙的时间。所以只有这种情况是假毫秒精度! |
| |
| 综上:如果使用gettimeofday系统调用,只要不要使用节拍计数器就可以保证达到微妙精度的时间(刨除进程上下文时间误差)。至于网上说的可以拿到纳秒精度的时间,看起来都是错的。除非通过修改内核,使用时间戳计数器实现。Over! |
| |
| |
| 最后最后说一个事情:jiffies的定义的是4字节,你可能猜想它初始值是0。实际上,事实并非如此!linux中jiffies被初始化为0xfffb6c20,它是一个32位有符号数,正好等于-300 000。因此,计数器会在系统启动5分钟内溢出。这是为了使对jiffies溢出处理有缺陷的内核代码在开发阶段被发现,避免此类问题出现在稳定版本中。 |
| |
| |
| |
| 参考《深入理解linux内核》 |
| |
| |
| #include <stdio.h> |
| |
| #include <iostream> |
| #include <ctime> |
| #include <string> |
| |
| #include <sys/time.h> |
| |
| |
| using namespace std; |
| |
| int main() |
| { |
| time_t beginTime,endTime; |
| struct timeval tvBegin,tvEnd; |
| struct timezone tz; |
| |
| |
| time (&beginTime); |
| printf ("The current local time is: %s", ctime (&beginTime)); |
| gettimeofday (&tvBegin , &tz); |
| |
| |
| |
| time (&endTime); |
| printf ("The current local time is: %s \n", ctime (&endTime)); |
| gettimeofday (&tvEnd , &tz); |
| |
| |
| |
| |
| |
| |
| |
| uint64_t uSpendms = (tvEnd.tv_sec-tvBegin.tv_sec)*1000+(tvEnd.tv_usec-tvBegin.tv_usec)/1000; |
| |
| |
| #ifdef TEST |
| printf ("uSpendms = %lu .tvEnd.tv_sec = %ld, tvBegin.tv_sec=%ld ; tvEnd.tv_usec = %ld, tvBegin.tv_usec=%ld .\n", |
| uSpendms, |
| tvEnd.tv_sec, tvBegin.tv_sec, |
| tvEnd.tv_usec, tvBegin.tv_usec); |
| #else |
| std::cout<< "tvEnd.tv_sec ="<<tvEnd.tv_sec << ". tvBegin.tv_sec ="<<tvBegin.tv_sec <<std::endl; |
| std::cout<< "tvEnd.tv_usec ="<<tvEnd.tv_usec << ". tvBegin.tv_usec ="<<tvBegin.tv_usec <<std::endl; |
| std::cout<< "SpendTime="<<uSpendms<<"ms"<<std::endl; |
| |
| #endif |
| |
| |
| |
| #ifdef WRITE_LOG |
| |
| int i; |
| double result; |
| |
| |
| FILE* fout = fopen(“test.log”, "w"); |
| if (!fout) { |
| return 1; |
| } |
| |
| |
| fprintf(fout, "double sqrtTable[] = {\n"); |
| for (i = 0; i < 10; ++i) { |
| result = sqrt(static_cast<double>(i)); |
| fprintf(fout, "%g,\n", result); |
| } |
| |
| |
| fprintf(fout, "0};\n"); |
| fclose(fout); |
| |
| #endif |
| |
| |
| |
| return 0; |
| |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)