1. 概述
在 Kotlin 中进行性能分析或执行耗时统计时,我们常常需要测量一段代码的执行时间。本文将介绍几种常见方法,并分析它们的适用场景和底层机制。
我们会先介绍现代系统中两种主要的时钟类型:时间点时钟(Wall Clock) 和 单调时钟(Monotonic Clock),然后依次讲解 Kotlin 提供的几个时间测量 API,包括:
measureTimeMillis
measureNanoTime
measureTime
/measureTimedValue
(kotlin.time 包)- 原始 Java 方式(System.nanoTime / currentTimeMillis)
2. 时钟类型简介
现代系统中通常有两种类型的时钟用于时间测量:
2.1 时间点时钟(Wall Clock)
这种时钟反映的是实际的日期和时间,例如 System.currentTimeMillis()
返回的就是从 1970 年 1 月 1 日 00:00:00 UTC 到现在的毫秒数。
⚠️ 缺点:
时间点时钟可能会因为以下原因出现时间回退或跳跃:
- NTP 同步
- 虚拟机暂停
- 系统时间被手动修改
- 闰秒处理
因此,不建议用于计算时间间隔,因为有可能出现负数结果。
2.2 单调时钟(Monotonic Clock)
这类时钟通常基于系统启动时间,如 System.nanoTime()
,它返回的是自 JVM 启动以来的纳秒数。
✅ 优点:
- 保证只增不减
- 不受系统时间调整影响
- 更适合用于测量代码执行时间
3. 使用 measureTimeMillis
Kotlin 标准库提供了一个便捷函数 measureTimeMillis
,用于测量一段代码的执行时间(单位:毫秒):
val elapsed = measureTimeMillis {
Thread.sleep(100)
println("Measuring time via measureTimeMillis")
}
assertThat(elapsed).isGreaterThanOrEqualTo(100)
⚠️ 注意:该方法使用的是 System.currentTimeMillis()
,属于 Wall Clock,不推荐用于精确时间测量。
4. 使用 measureNanoTime
如果你需要更高精度的时间测量,可以使用 measureNanoTime
:
val elapsed = measureNanoTime {
Thread.sleep(100)
println("Measuring time via measureNanoTime")
}
assertThat(elapsed).isGreaterThanOrEqualTo(100 * 1_000_000)
✅ 优点:使用 System.nanoTime()
,属于 Monotonic Clock,推荐用于精确时间测量
⚠️ 缺点:返回值是 Long
类型的纳秒数,使用上不如 Duration
类型直观
5. 使用 TimeSource
API(Kotlin 1.3+)
从 Kotlin 1.3 开始,引入了 kotlin.time
包,提供了更现代、类型安全的 API 来处理时间测量。
5.1 measureTime
@Test
@ExperimentalTime
fun `Given a block of code, When using measureTime, Then reports the elapsed time as a Duration`() {
val elapsed: Duration = measureTime {
Thread.sleep(100)
println("Measuring time via measureTime")
}
assertThat(elapsed).isGreaterThanOrEqualTo(100.toDuration(DurationUnit.MILLISECONDS))
}
✅ 优点:
- 返回
Duration
类型,语义更清晰 - 支持链式操作、比较、加减等运算
- 使用
TimeSource.Monotonic
,基于 Monotonic Clock
⚠️ 注意:该 API 曾是实验性的,需添加 @ExperimentalTime
注解。从 Kotlin 1.7 开始,ExperimentalTime
已被弃用,但仍需注意兼容性。
5.2 measureTimedValue
如果你不仅需要测量时间,还需要获取代码块的返回值,可以使用 measureTimedValue
:
val (value, elapsed) = measureTimedValue {
Thread.sleep(100)
42
}
assertThat(value).isEqualTo(42)
assertThat(elapsed).isGreaterThanOrEqualTo(100.toDuration(DurationUnit.MILLISECONDS))
也可以不使用解构语法,直接获取 TimedValue
:
val timedValue: TimedValue<Int> = measureTimedValue {
Thread.sleep(100)
42
}
assertThat(timedValue.value).isEqualTo(42)
assertThat(timedValue.duration).isGreaterThanOrEqualTo(100.toDuration(DurationUnit.MILLISECONDS))
6. 使用 Java 原生方法
虽然 Kotlin 提供了更高级的封装,但在某些场景下,我们仍可以直接使用 Java 的方式:
val start = System.nanoTime()
Thread.sleep(100)
assertThat(System.nanoTime() - start).isGreaterThanOrEqualTo(100 * 1_000_000)
✅ 优点:简单直接,适用于所有 Kotlin/Java 项目
⚠️ 建议:优先使用 nanoTime()
而非 currentTimeMillis()
,避免时间跳跃问题
7. 总结
方法 | 返回类型 | 时钟类型 | 是否推荐 |
---|---|---|---|
measureTimeMillis |
Long (毫秒) |
Wall Clock ❌ | ❌ |
measureNanoTime |
Long (纳秒) |
Monotonic ✅ | ✅ |
measureTime |
Duration |
Monotonic ✅ | ✅ 推荐 |
measureTimedValue |
TimedValue<T> |
Monotonic ✅ | ✅ 需要返回值时推荐 |
Java 原生方式 | Long |
可选 | ✅ 通用方案 |
✅ 推荐做法:使用 measureTime
或 measureTimedValue
,语义清晰且基于 Monotonic Clock,适合大多数时间测量场景。
⚠️ 踩坑提醒:不要使用 measureTimeMillis
来做性能分析,它基于 Wall Clock,可能受系统时间影响,出现负数或不准确的结果。
完整示例代码已上传至 GitHub:查看源码