Macos 如何使用马赫绝对时间而不溢出?

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绝对时间乘以该分数以获得纳秒值) 我们通常希望将

在达尔文,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绝对时间
乘以该分数以获得纳秒值)

我们通常希望将精确的刻度转换为“标准”(十进制)单位,但不幸的是,将绝对时间乘以分数即使在64位算术中也会溢出。这是一个错误,苹果公司关于绝对马赫数时间的唯一一份文档落入了()的陷阱

我应该如何编写一个正确使用
mach\u absolute\u time
的函数


  • 请注意,这不是一个理论问题:QA1398中的示例代码完全无法在基于PowerPC的Mac上工作。在英特尔Mac电脑上,
    mach\u timebase\u info
    始终返回1/1作为缩放因子,因为CPU的原始刻度计数不可靠(动态速度步进),因此API会为您进行缩放。在PowerPC Mac上,
    mach_timebase_info
    返回100000000/3333333 5或100000000/25000000,因此苹果提供的代码肯定每隔几分钟就会溢出一次。哎呀
  • 最精确(最佳)答案 以128位精度执行算术以避免溢出

    //返回以纳秒为单位的单调时间,从函数第一次运行时开始测量
    //在过程中调用。
    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)