32位机器clock_t溢出问题及跨平台CPU时间统计方案咨询
一、32位AIX上溢出场景下的计算可信度分析
在32位AIX中,clock_t是有符号32位整数,最大值为2^31-1,对应约35分47秒(因CLOCKS_PER_SEC=1e6,2^31-1微秒≈35.79分钟)。当beg对应35分43秒(未达最大值),work()运行10秒后,end会溢出变为负数(多数AIX实现按补码循环处理有符号整数溢出)。
此时计算end - beg会得到负数,强制转换为u64(无符号64位整数)后,负数会被解析为极大的无符号值,完全偏离实际10秒的CPU时长,本次计算完全不可信。
后续调用f()时,只要程序运行时间超过35分47秒,end和beg都会处于溢出后的负数状态:虽然单次end - beg的补码运算结果可能看似符合单次时长,但之前已经累加了错误的大值,后续累计结果会彻底失真,因此后续计算也不可信。
二、替代方案(兼容RedHat和32位AIX,微秒精度)
方案1:手动修正溢出差值
利用有符号整数溢出的补码特性,在代码中对差值做溢出修正:
typedef unsigned long long u64; u64 accum_ticks = 0; const clock_t CLOCK_MAX = (clock_t)0x7FFFFFFF; // 32位有符号整数最大值 void f() { clock_t beg = clock(); work(); clock_t end = clock(); clock_t diff = end - beg; // 溢出判断:end < beg说明发生补码循环,差值需加上2^32修正 if (end < beg) { accum_ticks += (u64)(diff + ((u64)1 << 32)); } else { accum_ticks += (u64)diff; } } u64 elapsed_CPU_us() { return accum_ticks * 1000000 / CLOCKS_PER_SEC; }
原理:32位有符号整数溢出后,end的实际值为存储值 + 2^32,因此实际差值实际值 - beg = (end - beg) + 2^32,通过end < beg触发修正逻辑。
方案2:使用AIX内核的getrusage()接口
AIX内核头文件支持getrusage(),可直接获取进程累计CPU时间,精度到微秒:
#include <sys/resource.h> typedef unsigned long long u64; u64 accum_ticks = 0; void f() { struct rusage beg_rusage, end_rusage; getrusage(RUSAGE_SELF, &beg_rusage); work(); getrusage(RUSAGE_SELF, &end_rusage); // 转换为微秒级时间戳 u64 beg_us = (u64)beg_rusage.ru_utime.tv_sec * 1000000 + beg_rusage.ru_utime.tv_usec; u64 end_us = (u64)end_rusage.ru_utime.tv_sec * 1000000 + end_rusage.ru_utime.tv_usec; accum_ticks += (end_us - beg_us); } u64 elapsed_CPU_us() { return accum_ticks; }
说明:ru_utime记录进程累计用户态CPU时间,tv_sec为秒数,tv_usec为微秒数,组合后直接得到微秒级时间戳,通过u64存储可避免溢出问题。
方案3:使用AIX的clock_gettime()(版本兼容前提下)
部分AIX版本支持clock_gettime(),可获取进程CPU时间,精度到纳秒:
#include <time.h> typedef unsigned long long u64; u64 accum_ticks = 0; void f() { struct timespec beg, end; clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &beg); work(); clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &end); // 转换为微秒级时间戳 u64 beg_us = (u64)beg.tv_sec * 1000000 + beg.tv_nsec / 1000; u64 end_us = (u64)end.tv_sec * 1000000 + end.tv_nsec / 1000; accum_ticks += (end_us - beg_us); } u64 elapsed_CPU_us() { return accum_ticks; }
注意:需确认目标AIX版本是否支持CLOCK_PROCESS_CPUTIME_ID,该方式更符合POSIX标准,精度更高。
内容的提问来源于stack exchange,提问作者ABu

