一、先理解它的定位:一个被标记为Legacy的计时接口
process.hrtime([time])在Node.js官方文档中的稳定性标记是3–Legacy,文档原文写道:“改用process.hrtime.bigint()。”
这不是说它不能用了,而是说它的设计形态在BigInt进入JavaScript之后显得过时。hrtime()返回的是[seconds,nanoseconds]元组数组,差值计算需要手动处理秒与纳秒的进位;hrtime.bigint()返回单个BigInt纳秒值,两次调用直接相减即可得到差值。在2026年的Node.js代码中,新建的计时逻辑应当优先选择bigint()版本,hrtime()的存在意义主要是兼容旧代码和理解计时模型本身。
围绕这个接口的工程讨论,核心不在于“怎么调用”,而在于理解它返回的时间是什么、不是什么,以及什么时候该用另一个接口。
二、返回值的真实含义:单调时间,不是墙上时钟
process.hrtime()返回的高精度时间相对于过去的某个任意时间点,与一天中的时刻无关,因此不受系统时钟调整的影响。这一点是它与Date.now()的分水岭。
Date.now()返回Unix时间戳,可能因为NTP同步、手动修改系统时间、闰秒处理等原因发生跳变。用Date.now()测量代码执行时间,如果测量区间内发生时钟回拨,差值可能为负;如果时钟向前跳,差值会被拉长。hrtime()的单调性排除了这类干扰,它在测量间隔耗时的场景中是更可靠的选择。
但这也意味着hrtime()的返回值不能用来获取当前日期或时间。它只告诉你“从某个起点到现在过了多少”,不告诉你“现在是几点”。需要墙上时钟时用Date,需要测量间隔时用hrtime,两者职责不重叠。
三、time参数的作用与约束
process.hrtime(time?)的可选参数time必须是上一次process.hrtime()调用的返回值。传入后,方法返回的是当前时间与传入时间之间的差值,同样以[seconds,nanoseconds]形式表示。
约束在于类型:如果传入的不是元组数组,抛出TypeError。如果传入的是用户自定义的数组(格式看起来像但并非来自hrtime()调用),行为未定义。这意味着不能手工构造一个[0,0]作为“起始时间”传入——虽然在某些场景下碰巧能工作,但文档不承诺这种行为。
const { hrtime } = require('node:process');
const start = hrtime();
// 执行一些同步操作
const diff = hrtime(start);
console.log(`耗时: ${diff[0]} 秒 ${diff[1]} 纳秒`);
如果diff[1]为负(跨秒边界时秒部分进位),直接输出会得到不直观的结果。更稳妥的写法是换算为单一单位:
const totalNs = diff[0] * 1e9 + diff[1];
console.log(`耗时: ${totalNs / 1e6} 毫秒`);
四、PHPhrtime()的对照与差异
PHP从7.3.0起在核心中提供了hrtime()函数。它的语义与Node.js版本在核心层面一致:返回单调的高精度时间,不受系统时钟调整影响。
差异在返回形态和参数设计上。PHP的hrtime()接受一个布尔参数$as_number:默认为false时返回[seconds,nanoseconds]数组;传true时返回纳秒整数(64位平台)或浮点数(32位平台)。PHP没有hrtime.bigint()这样的独立方法,也没有“传入上次结果计算差值”的设计——差值由调用方自行用两次返回值相减。
PHP社区中有一个值得注意的经验记录:在KVM、XEN等虚拟化平台上,如果缺少vDSO支持,使用microtime()会因每次调用触发用户态到内核态的上下文切换而显著增加开销,此时hrtime()是更合适的选择。这一背景有助于理解高精度计时函数在服务端场景中的实际价值,虽然Node.js的hrtime底层实现路径与PHP不同,但“避免非单调、高开销的计时方式”这一原则是相通的。
五、hrtime.bigint():当前应优先选择的形态
process.hrtime.bigint()在Node.jsv10.7.0中引入。它返回一个BigInt类型的纳秒值,不接收time参数,因为差值可以直接通过两个BigInt相减得到。
const start = process.hrtime.bigint();
// 执行操作
const end = process.hrtime.bigint();
const elapsedNs = end - start; // BigInt
console.log(`耗时: ${Number(elapsedNs) / 1e6} 毫秒`);
与hrtime()的元组方式相比,bigint()消除了进位处理的认知负担,也避免了[seconds,nanoseconds]手工换算时的单位错误。对于纳秒级精度的需求,BigInt的数值范围足够覆盖极长的测量区间,不存在元组方式中秒与纳秒分离带来的表达冗余。
从迁移角度看,如果现有代码只调用hrtime()获取当前时间点而不传time参数做差值,替换为hrtime.bigint()并在需要时转换类型即可。如果代码依赖time参数计算差值,替换为“保存起始BigInt→结束时相减”的模式更为直接。
六、单元测试与边界场景
验证单调性
const assert = require('assert');
const t1 = process.hrtime.bigint();
const t2 = process.hrtime.bigint();
assert(t2 >= t1, '高精度时间应单调不减');
单调性是hrtime系列的核心保证。单元测试中可以直接断言后一次调用不小于前一次。
验证time参数类型错误
assert.throws(
() => process.hrtime([0, 0]),
TypeError,
'传入非 hrtime 返回值应抛出 TypeError'
);
注意:文档说的是“不是元组数组”时抛出TypeError,而“传入用户自定义数组”时行为未定义。上述断言依赖具体实现,在单元测试中使用时需要确认当前Node.js版本的实际行为。更稳妥的测试策略是避免依赖未定义行为的断言。
测量已知延迟
const start = process.hrtime.bigint();
setTimeout(() => {
const elapsed = process.hrtime.bigint() - start;
const ms = Number(elapsed) / 1e6;
console.assert(ms >= 490 && ms < 600, `预期约 500ms,实际 ${ms}ms`);
}, 500);
setTimeout的延迟不精确,断言范围需要留出余量。这个测试验证的是hrtime能正确反映实际经过的时间,而非验证定时器的精度。
边界:跨秒元组的计算
如果坚持使用hrtime()元组,测试跨秒场景:
const start = process.hrtime();
// 等待略超过 1 秒
const end = process.hrtime(start);
const totalNs = end[0] * 1e9 + end[1];
console.assert(totalNs > 1e9, '跨秒后总纳秒应大于 10 亿');
这个测试的意义在于确认end[0]和end[1]的组合逻辑正确,而不是分别使用两个分量。
七、常见误区速查
误区一:process.hrtime()返回的是当前时间戳。返回的是相对于任意起点的单调时间元组,不能用于日志时间戳或日期显示。需要墙上时钟用newDate()或Date.now()。
误区二:process.hrtime()仍然是新建计时逻辑的优选。文档已将其标记为Legacy,建议改用hrtime.bigint()。新代码用BigInt版本可以避免元组进位处理。
误区三:hrtime()的精度保证是“纳秒级准确”。返回值的单位是纳秒,但底层时钟的实际分辨率取决于操作系统和硬件,不一定达到1纳秒的物理精度。单位精度与测量精度是两件事。
误区四:可以手工构造[0,0]传给hrtime()作为起始点。文档明确说用户自定义数组会导致未定义行为。起始点必须来自真实的hrtime()调用。
误区五:hrtime()和hrtime.bigint()可以混用差值计算。两者返回类型不同,不能直接相减。要么全程用元组,要么全程用BigInt,不要在一个测量流程中混用。
八、性能考量:什么时候值得关心计时开销本身
hrtime系列比Date.now()的调用成本略高,因为它需要访问更高分辨率的时钟源。但在绝大多数场景中,一次hrtime()调用的开销相对于被测量的代码块本身可以忽略。
需要留意的是在极短间隔的循环中反复调用的情况。如果测量逻辑本身被放在每秒执行数十万次的路径中,计时开销可能开始影响被测代码的耗时。在这种场景中,可以考虑降低采样频率,或者接受计时开销作为测量的一部分(当所有对比组使用相同的计时方式时,相对比较仍然有意义)。
另一个实际经验是:在容器化环境中,hrtime的单调性保证仍然成立,但其底层时钟源可能因虚拟化层而存在分辨率差异。如果测量结果出现不符合预期的粒度(例如所有测量值都是某个固定毫秒数的整数倍),值得检查运行环境的时钟源配置,而不是怀疑代码。
九、练习与思考
练习一:分别用Date.now()和process.hrtime.bigint()测量同一段同步代码(例如对一个数组排序)的执行时间。循环执行若干次,观察Date.now()是否出现差值为0的情况,而hrtime.bigint()是否能区分出差异。
练习二:写一个Stopwatch类,内部使用process.hrtime.bigint()记录起始时间,提供elapsed()方法返回自启动以来经过的纳秒数(BigInt)和毫秒数(Number)。思考:如果需要支持暂停和恢复,内部状态应该记录什么。
练习三:在PHP环境中使用hrtime()测量一段循环的耗时,与Node.js的process.hrtime.bigint()测量类似逻辑的耗时做对比。重点不在于比较绝对数值(运行环境不同),而在于理解两者在差值计算方式上的差异:PHP需要手动相减两次返回值,Node.js的BigInt版本同样手动相减,而hrtime()的time参数模式则是另一种思路。
十、延伸阅读与参考文献
-
Node.js官方文档,《process.hrtime([time])》,https://beta.docs.nodejs.org/process/hrtime#processhrtimetime。权威API说明,明确标注Stability3–Legacy、
time参数的类型约束、单调时间语义以及“改用hrtime.bigint()”的官方建议。 -
Node.js中文文档,《process.hrtime.bigint()》,https://url.nodejs.cn/api/v26/process/process_hrtime_bigint.html。BigInt版本的说明,解释为何不需要
time参数:差值通过两个BigInt相减即可得到。 -
Node.jsv10.7.0发布公告,https://nodejs.org/ko/blog/release/v10.7.0。记录了
process.hrtime.bigint()的引入版本,可作为版本兼容性判断的依据。 -
PHP官方文档,《hrtime》,https://static.php.net/function.hrtime。PHP核心高精度计时函数的说明,包含
$as_number参数的行为和32位/64位平台的返回类型差异。 -
PHP官方文档,《Highresolutiontiming》,https://static.php.net/manual/it/book.hrtime.php。HRTime扩展的StopWatch类说明,展示了PHP生态中高精度计时的另一种封装形态。