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 可选 ✅ 通用方案

推荐做法:使用 measureTimemeasureTimedValue,语义清晰且基于 Monotonic Clock,适合大多数时间测量场景。

⚠️ 踩坑提醒:不要使用 measureTimeMillis 来做性能分析,它基于 Wall Clock,可能受系统时间影响,出现负数或不准确的结果。

完整示例代码已上传至 GitHub:查看源码


原始标题:Measuring Elapsed Time in Kotlin