qinghuan
← 回到知识探索

计算机趣闻 / 案例拆解

程序明明跑完了,耗时为什么可能是负数?

日历时钟回答“现在几点”,单调时钟帮助回答“经过多久”。

展开本文目录
  1. 先动手看看
  2. 先确定你要记录哪一种时间
  3. 用两组数字演一遍,不必真的改电脑时间
  4. 两个数能相减,前提是它们用同一把尺
  5. 秒表也有适用范围
  6. 记住这三点
  7. 别把例子用过头
  8. 自己试一小步
  9. 查原始资料
  10. 找一份资料继续练
开始前,会这些就够了
  • 知道开始值与结束值相减能得到差值
遇到不熟悉的词,先看这 2 个解释
时间戳
时间戳是用来标记某个时间点的数据。常见的一种做法是选定起点,再记录从起点算起的秒数或毫秒数;交换数据时,双方必须知道起点、单位和采用的时间规则。
单调时钟
单调时钟用于量出事情经过了多久,读数不会因为用户校正日期或系统调整日历时间而倒退。通常只比较同一时钟的两次读数,不把某次读数解释成具体年月日。
打开完整术语小抄 ↗

设想一段程序开始时记录电脑时间,结束后再减一次,日志却写着耗时负五百毫秒。程序没有穿越。一个可能的原因是运行期间系统时钟被向后校正了。这个假设案例能帮助我们区分两种名字都带时间、用途却不同的数值。

只改日历时钟,耗时会怎样?

任务实际经过多久和日历时钟被校正多少,是两个独立输入。这里全部使用假设数字。

同一任务的两组假设读数(毫秒)
时钟开始结束相减
日历100009500-500
单调80008500500

日历读数相减得到 -500 毫秒,同一单调时钟的读数相减得到 500 毫秒。负数来自时钟被向后校正,不是任务倒着运行。

按钮和滑块不读取或修改系统时间。不同机器、不同起点的单调读数不能直接相减;本模型也没有模拟休眠和计时分辨率。

先确定你要记录哪一种时间

墙上时间指能对应日期与钟点的时间,例如日志中的某天十点二十分。JavaScript 的 Date.now() 返回从约定起点到现在的毫秒数,依赖系统时钟。为了与真实日期保持一致,这个时钟可能被用户或校时服务调整,读数不保证始终向前。

单调时钟更像秒表,它的重点是同一时间基准下读数不倒退,而不是告诉你今天几号。浏览器的 performance.now() 返回相对自身时间起点的读数;Python 的 time.monotonic() 也用来比较经过的时长。两次相减有意义,单独一个读数未必能直接解释成日历日期。

用两组数字演一遍,不必真的改电脑时间

下面假设任务实际经过五百毫秒,但在这期间墙上时间被向后调了一千毫秒。日历读数净变化就是负五百,而秒表读数仍增加五百。所有数字都是为解释问题安排的模型输入,不是对你的电脑进行测量,也不会触碰系统设置。

如果只保留最终那个负数,就可能把错误归因于减法或业务代码。更有用的记录是同时保留用于展示的日期、用于计算耗时的差值,并注明单位和来源。这样查看日志的人才能知道,哪一个字段在回答发生时间,哪一个在回答持续多久。

小实验 · javascript
const wallStart = 10000;
const elapsed = 500;
const clockAdjustment = -1000;
const wallEnd = wallStart + elapsed + clockAdjustment;
const monoStart = 8000;
const monoEnd = monoStart + elapsed;
console.log(wallEnd - wallStart);
console.log(monoEnd - monoStart);

怎样运行:在浏览器控制台或 Node.js 中运行。它只计算预设数字,不读取或修改系统时间。

运行后对照结果
-500
500

两个数能相减,前提是它们用同一把尺

一个网页打开后过了一秒,另一个网页可能已经打开一小时。若各自从不同起点计数,直接把它们的 performance.now() 相减,并不能得到某次网络请求的耗时。不同机器的单调读数也没有天然共享的零点,不能拿来替代跨机器的日期时间。

单位也要看清:浏览器这个接口用毫秒,Python 的 monotonic() 用秒,而带 _ns 的接口用纳秒表示。显示更多小数不保证测量更准确;浏览器还可能出于隐私和安全原因降低时间分辨率。很短操作连续测两次得到相同读数,并不表示它真的不耗时间。

秒表也有适用范围

测量时长之前,还要决定电脑休眠的那段时间算不算进去。不同 API、操作系统与浏览器对休眠期间计时的支持可能有差别,不能把“单调”直接理解为任何场景下都与现实经过的时间完全一致。较长的倒计时应把暂停和恢复的规则写清楚。

同样,预约回调在一秒后运行,不等于它恰好在第一千毫秒获得执行机会。其他任务会影响调度。若要比较两段程序快慢,应采用同一种时钟,重复观察并保持输入和环境尽量一致;一次小差值只能作为线索,不能替代可靠的性能结论。

记住这三点

  • 记录日期与测量持续时间是两种需求,应选择对应的时钟。
  • 相减前要确认读数的起点、单位和运行范围相同。
  • 单调不等于无限精确,也不自动决定休眠和任务调度行为。

别把例子用过头

  • 示例是时钟跳变的计算模型,没有宣称发生过一次真实故障。
  • 不能把不同设备或不同起点的单调读数直接相减。
  • 日期格式或时区的显示变化,与系统时钟实际调整不是同一件事。

自己试一小步

为一个本地任务设计日志,你会保存哪些时间字段?

  1. 分别列出用户需要看到的发生日期和你需要分析的耗时。
  2. 为每个字段注明来源、单位和可比较范围。
  3. 补充任务中途休眠、程序重启或日历时间被调整时的处理规则。

做到这些,就算完成

  • 没有用一个字段同时冒充日历时间与可靠持续时长。
  • 能解释哪些差值可以直接计算,哪些需要额外约定。
  • 不需要真的修改系统时间来完成推演。

查原始资料

想确认一个细节,先看这份资料对应的部分,不必从头读完所有文档。

找一份资料继续练

先选一项,按下面的范围继续学;不必从头读完整门课程。