Macos 如何使用马赫绝对时间而不溢出?
在达尔文,POSIX标准Macos 如何使用马赫绝对时间而不溢出?,macos,x86,posix,powerpc,darwin,Macos,X86,Posix,Powerpc,Darwin,在达尔文,POSIX标准clock\u gettime(clock\u MONOTONIC)计时器不可用。相反,最高分辨率的单调计时器是通过mach/mach\u time.h中的mach\u absolute\u time函数获得的 返回的结果可能是处理器未调整的滴答数,在这种情况下,时间单位可能是奇怪的倍数。例如,在具有33MHz时钟计数的CPU上,Darwin返回100000000/3333333 5作为返回结果的精确单位(即,将mach\u绝对时间乘以该分数以获得纳秒值) 我们通常希望将
clock\u gettime(clock\u MONOTONIC)
计时器不可用。相反,最高分辨率的单调计时器是通过mach/mach\u time.h
中的mach\u absolute\u time
函数获得的
返回的结果可能是处理器未调整的滴答数,在这种情况下,时间单位可能是奇怪的倍数。例如,在具有33MHz时钟计数的CPU上,Darwin返回100000000/3333333 5作为返回结果的精确单位(即,将mach\u绝对时间
乘以该分数以获得纳秒值)
我们通常希望将精确的刻度转换为“标准”(十进制)单位,但不幸的是,将绝对时间乘以分数即使在64位算术中也会溢出。这是一个错误,苹果公司关于绝对马赫数时间的唯一一份文档落入了()的陷阱
我应该如何编写一个正确使用mach\u absolute\u time
的函数
mach\u timebase\u info
始终返回1/1作为缩放因子,因为CPU的原始刻度计数不可靠(动态速度步进),因此API会为您进行缩放。在PowerPC Mac上,mach_timebase_info
返回100000000/3333333 5或100000000/25000000,因此苹果提供的代码肯定每隔几分钟就会溢出一次。哎呀//返回以纳秒为单位的单调时间,从函数第一次运行时开始测量
//在过程中调用。
uint64\u t单调时间纳米(){
uint64_t now=马赫绝对时间();
静态结构数据{
数据(uint64)偏差:偏差(偏差){
内核返回时间=马赫时基信息(&tb);
断言(mtiStatus==KERN_SUCCESS);
}
uint64_t标度(uint64_t i){
返回刻度高精度(i-偏差,tb.numer,tb.denom);
}
静态uint64刻度高精度(uint64刻度,uint32刻度数字,
uint32(最新版本){
U64高=(i>>32)*数字;
U64低=(i&0xFFFFFFFFFFFULL)*数字/名称;
U64 highRem=((高%denom)0);
}
}
马赫时基信息数据tb;
uint64_t偏差;
}数据(现在);
返回值(现在-data.bias)*data.tb.numer/data.tb.denom;
}
使用低精度算法但使用连分数避免精度损失的精细解决方案
//此函数返回给定间隔内的有理数
//最小的分母(和最小的分子);正确性
//证明忽略了浮点错误)。
静态马赫数时基信息数据最佳分数(双a双b){
if(楼层(a)<楼层(b))
{mach_timebase_info_data_t rv={(int)ceil(a),1};返回rv;}
双m=楼层(a);
马赫时基信息数据下一步=最佳分数(1/(b-m),1/(a-m));
mach_timebase_info_data_t rv={(int)m*next.numer+next.denum,next.numer};
返回rv;
}
//返回以纳秒为单位的单调时间,从函数第一次运行时开始测量
//在过程中调用。时钟运行速度可能快或慢0.1%
//比“精确”的滴答声计数还要多。但是,尽管错误的界限是
//与实用答案相同,错误实际上是随着时间的推移而最小化的
//给定的精度范围。
uint64\u t单调时间纳米(){
uint64_t now=马赫绝对时间();
静态结构数据{
数据(uint64)偏差:偏差(偏差){
内核返回时间=马赫时基信息(&tb);
断言(mtiStatus==KERN_SUCCESS);
双分形=(双)tb.numer/tb.denom;
uint64\u t span目标=315360000000000000llu;//10年
if(getExpressibleSpan(tb.numer,tb.denom)>=spanTarget)
返回;
对于(双errorTarget=1/1024.0;errorTarget>0.000001;){
马赫时基信息数据新压裂=
最佳分数((1-错误目标)*分数,(1+错误目标)*分数);
if(getExpressibleSpan(newFrac.numer,newFrac.denom)=spanTarget);
}
马赫时基信息数据tb;
uint64_t偏差;
}数据(现在);
返回值(现在-data.bias)*data.tb.numer/data.tb.denom;
}
推导
我们的目标是将mach\u timebase\u info
返回的分数减少到基本相同但分母较小的分数。我们可以处理的时间跨度的大小仅受分母大小的限制,而不受我们将乘以的分数的分子的限制:
uint64\u t getExpressibleSpan(uint32\u t numer,uint32\u t denom){
//这比我们能用numer乘以的最小值还要小
//溢出。ceilLog2(numer)=64-numer的前导零数
uint64\u t maxDiffWithoutOverflow=((uint64\u t)1当与用于转换为纳秒的mach\u timebase\u info
struct中的值相乘/相除时,您担心溢出。因此,虽然它可能不适合您的确切需要,但有更简单的方法可以在纳秒或秒内获得计数
以下所有解决方案都在内部使用马赫绝对时间
(而不是挂钟)
使用double
代替uint64\u t
(由Objective-C和Swift支持)
(如果需要纳秒,请卸下1e-9
)
用法:
uint64_t start = mach_absolute_time();
// do something
uint64_t stop = mach_absolute_time();
double durationInSeconds = tbInSeconds * (stop - start);
使用ProcessInfo.ProcessInfo。
(由Objective-C和Swift支持)
它直接在两次秒内完成工作:
CFTimeInterval start = NSProcessInfo.processInfo.systemUptime;
// do something
CFTimeInterval stop = NSProcessInfo.processInfo.systemUptime;
NSTimeInterval durationInSeconds = stop - start;
作为参考,
只需执行与上一个解决方案类似的操作:
struct mach_timebase_info info;
mach_timebase_info(&info);
__CFTSRRate = (1.0E9 / (double)info.numer) * (double)info.denom;
__CF1_TSRRate = 1.0 / __CFTSRRate;
uint64_t tsr = mach_absolute_time();
return (CFTimeInterval)((double)tsr * __CF1_TSRRate);
使用石英砂。
(由Objective-C和Swift支持)
与系统正常运行时间相同,但
struct mach_timebase_info info;
mach_timebase_info(&info);
__CFTSRRate = (1.0E9 / (double)info.numer) * (double)info.denom;
__CF1_TSRRate = 1.0 / __CFTSRRate;
uint64_t tsr = mach_absolute_time();
return (CFTimeInterval)((double)tsr * __CF1_TSRRate);
DispatchTime start = DispatchTime.now()
// do something
DispatchTime stop = DispatchTime.now()
TimeInterval durationInSeconds = Double(end.uptimeNanoseconds - start.uptimeNanoseconds) / 1_000_000_000
(result, overflow) = result.multipliedReportingOverflow(by: UInt64(DispatchTime.timebaseInfo.numer))
result = overflow ? UInt64.max : result / UInt64(DispatchTime.timebaseInfo.denom)