시간 측정

시간 측정

코드가 얼마나 걸리는지, 특정 시점이 지났는지 확인해야 할 때가 있어요. Kotlin 표준 라이브러리는 이런 시간 계산과 측정을 다양한 단위로 다룰 수 있는 도구를 제공해요. Duration으로 시간을 표현하는 방법부터 실제로 코드 실행 시간을 재는 방법까지 순서대로 살펴볼게요.

출처: Kotlin 공식 문서

본문

Kotlin 표준 라이브러리는 다양한 단위로 시간을 계산하고 측정할 수 있는 도구를 제공해요. 정확한 시간 측정은 다음과 같은 작업에 중요합니다.

  • 스레드나 프로세스 관리
  • 통계 수집
  • 타임아웃 감지
  • 디버깅

기본적으로 시간은 단조 시간 소스(monotonic time source)로 측정되지만, 다른 시간 소스도 설정할 수 있어요. 자세한 내용은 시간 소스 만들기 섹션을 참고하세요.

기간(Duration) 계산

시간의 양을 표현하기 위해 표준 라이브러리는 Duration 클래스를 제공해요. DurationDurationUnit 열거형 클래스의 다음 단위로 표현할 수 있어요.

  • NANOSECONDS
  • MICROSECONDS
  • MILLISECONDS
  • SECONDS
  • MINUTES
  • HOURS
  • DAYS

Duration은 양수, 음수, 0, 양의 무한대, 음의 무한대가 될 수 있어요.

기간 만들기

Duration을 만들려면 Int, Long, Double 타입에 사용 가능한 확장 속성인 nanoseconds, microseconds, milliseconds, seconds, minutes, hours, days를 사용해요.

Days(일)는 24시간의 기간을 뜻해요. 역법상의 날(calendar days)이 아니에요.

예를 들어:

import kotlin.time.*
import kotlin.time.Duration.Companion.nanoseconds
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.days

fun main() {
//sampleStart
    val fiveHundredMilliseconds: Duration = 500.milliseconds
    val zeroSeconds: Duration = 0.seconds
    val tenMinutes: Duration = 10.minutes
    val negativeNanosecond: Duration = (-1).nanoseconds
    val infiniteDays: Duration = Double.POSITIVE_INFINITY.days
    val negativeInfiniteDays: Duration = Double.NEGATIVE_INFINITY.days

    println(fiveHundredMilliseconds) // 500ms
    println(zeroSeconds)             // 0s
    println(tenMinutes)              // 10m
    println(negativeNanosecond)      // -1ns
    println(infiniteDays)            // Infinity
    println(negativeInfiniteDays)    // -Infinity
//sampleEnd
}

Duration 객체로 기본 산술 연산도 수행할 수 있어요.

import kotlin.time.*
import kotlin.time.Duration.Companion.seconds

fun main() {
//sampleStart
    val fiveSeconds: Duration = 5.seconds
    val thirtySeconds: Duration = 30.seconds

    println(fiveSeconds + thirtySeconds)
    // 35s
    println(thirtySeconds - fiveSeconds)
    // 25s
    println(fiveSeconds * 2)
    // 10s
    println(thirtySeconds / 2)
    // 15s
    println(thirtySeconds / fiveSeconds)
    // 6.0
    println(-thirtySeconds)
    // -30s
    println((-thirtySeconds).absoluteValue)
    // 30s
//sampleEnd
}

문자열 표현 얻기

Duration의 문자열 표현이 있으면 출력, 직렬화, 전송, 저장이 쉬워져서 유용해요.

문자열 표현을 얻으려면 .toString() 함수를 사용해요. 기본적으로 시간은 존재하는 각 단위로 보고돼요. 예: 1h 0m 45.677s 또는 -(6d 5h 5m 28.284s)

출력 형식을 구성하려면 원하는 DurationUnit과 소수점 자릿수를 함수 매개변수로 하는 .toString() 함수를 사용해요.

import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.DurationUnit

fun main() {
//sampleStart
    // 초 단위로 소수점 2자리까지 출력
    println(5887.milliseconds.toString(DurationUnit.SECONDS, 2))
    // 5.89s
//sampleEnd
}

ISO-8601 호환 문자열을 얻으려면 toIsoString() 함수를 사용해요.

import kotlin.time.Duration.Companion.seconds

fun main() {
//sampleStart
    println(86420.seconds.toIsoString()) // PT24H0M20S
//sampleEnd
}

기간 변환

Duration을 다른 DurationUnit으로 변환하려면 다음 속성을 사용해요.

  • inWholeNanoseconds
  • inWholeMicroseconds
  • inWholeSeconds
  • inWholeMinutes
  • inWholeHours
  • inWholeDays

예를 들어:

import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes

fun main() {
//sampleStart
    val thirtyMinutes: Duration = 30.minutes
    println(thirtyMinutes.inWholeSeconds)
    // 1800
//sampleEnd
}

또는 원하는 DurationUnit을 다음 확장 함수의 함수 매개변수로 사용할 수도 있어요.

  • .toInt()
  • .toDouble()
  • .toLong()

예를 들어:

import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit

fun main() {
//sampleStart
    println(270.seconds.toDouble(DurationUnit.MINUTES))
    // 4.5
//sampleEnd
}

기간 비교

Duration 객체가 같은지 확인하려면 동등 연산자(==)를 사용해요.

import kotlin.time.Duration
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.minutes

fun main() {
//sampleStart
    val thirtyMinutes: Duration = 30.minutes
    val halfHour: Duration = 0.5.hours
    println(thirtyMinutes == halfHour)
    // true
//sampleEnd
}

Duration 객체를 비교하려면 비교 연산자(<, >)를 사용해요.

import kotlin.time.Duration.Companion.microseconds
import kotlin.time.Duration.Companion.nanoseconds

fun main() {
//sampleStart
    println(3000.microseconds < 25000.nanoseconds)
    // false
//sampleEnd
}

기간을 구성 요소로 나누기

Duration을 시간 구성 요소로 나누고 추가 작업을 수행하려면 toComponents() 함수의 오버로드를 사용해요. 원하는 동작을 함수나 람다 표현식으로 함수 매개변수에 추가하면 돼요.

예를 들어:

import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes

fun main() {
//sampleStart
    val thirtyMinutes: Duration = 30.minutes
    println(thirtyMinutes.toComponents { hours, minutes, _, _ -> "${hours}h:${minutes}m" })
    // 0h:30m
//sampleEnd
}

이 예시에서 람다 표현식은 hoursminutes를 함수 매개변수로 갖고, 사용하지 않는 secondsnanoseconds 매개변수는 밑줄(_)로 표시해요. 이 표현식은 문자열 템플릿을 사용해 hoursminutes의 원하는 출력 형식을 만들고 이를 이어 붙인 문자열을 반환해요.

시간 측정

시간의 흐름을 추적하기 위해 표준 라이브러리는 다음을 쉽게 할 수 있는 도구들을 제공해요.

  • 원하는 시간 단위로 코드 실행에 걸린 시간 측정
  • 특정 시점 표시(mark)
  • 두 시점 비교와 뺄셈
  • 특정 시점 이후로 얼마나 시간이 지났는지 확인
  • 현재 시간이 특정 시점을 지났는지 확인

코드 실행 시간 측정

코드 블록 실행에 걸린 시간을 측정하려면 measureTime 인라인 함수를 사용해요.

import kotlin.time.measureTime

fun main() {
//sampleStart
    val timeTaken = measureTime {
        Thread.sleep(100)
    }
    println(timeTaken) // e.g. 103 ms
//sampleEnd
}

코드 블록 실행에 걸린 시간을 측정하면서 코드 블록의 값을 반환하려면 인라인 함수 measureTimedValue를 사용해요.

예를 들어:

import kotlin.time.measureTimedValue

fun main() {
//sampleStart
    val (value, timeTaken) = measureTimedValue {
        Thread.sleep(100)
        42
    }
    println(value)     // 42
    println(timeTaken) // e.g. 103 ms
//sampleEnd
}

두 함수 모두 기본적으로 단조 시간 소스를 사용해요.

시점 표시

특정 시점을 표시하려면 TimeSource 인터페이스와 markNow() 함수를 사용해 TimeMark를 만들어요.

import kotlin.time.*

fun main() {
   val timeSource = TimeSource.Monotonic
   val mark = timeSource.markNow()
}

시간 차이 측정

같은 시간 소스에서 온 TimeMark 객체 간의 차이를 측정하려면 뺄셈 연산자(-)를 사용해요.

같은 시간 소스에서 온 TimeMark 객체를 비교하려면 비교 연산자(<, >)를 사용해요.

예를 들어:

import kotlin.time.*

fun main() {
//sampleStart
   val timeSource = TimeSource.Monotonic
   val mark1 = timeSource.markNow()
   Thread.sleep(500) // 0.5초 동안 sleep
   val mark2 = timeSource.markNow()

   repeat(4) { n ->
       val mark3 = timeSource.markNow()
       val elapsed1 = mark3 - mark1
       val elapsed2 = mark3 - mark2

       println("Measurement 1.${n + 1}: elapsed1=$elapsed1, elapsed2=$elapsed2, diff=${elapsed1 - elapsed2}")
   }

   println(mark2 > mark1) // mark2가 mark1보다 나중에 캡처됐으므로 true
   // true
//sampleEnd
}

데드라인이 지났는지, 타임아웃에 도달했는지 확인하려면 hasPassedNow()hasNotPassedNow() 확장 함수를 사용해요.

import kotlin.time.*
import kotlin.time.Duration.Companion.seconds

fun main() {
//sampleStart
   val timeSource = TimeSource.Monotonic
   val mark1 = timeSource.markNow()
   val fiveSeconds: Duration = 5.seconds
   val mark2 = mark1 + fiveSeconds

   // 아직 5초가 지나지 않았어요
   println(mark2.hasPassedNow())
   // false

   // 6초 대기
   Thread.sleep(6000)
   println(mark2.hasPassedNow())
   // true

//sampleEnd
}

시간 소스

기본적으로 시간은 단조 시간 소스를 사용해 측정돼요. 단조 시간 소스는 앞으로만 이동하며 시간대 같은 변동의 영향을 받지 않아요. 단조 시간의 대안은 경과 실시간(elapsed real time)인데, 이를 벽시계 시간(wall-clock time)이라고도 불러요. 경과 실시간은 다른 시점을 기준으로 측정되죠.

플랫폼별 기본 시간 소스

이 표는 각 플랫폼의 단조 시간 기본 소스를 설명해요.

플랫폼 소스
Kotlin/JVM System.nanoTime()
Kotlin/JS (Node.js) process.hrtime()
Kotlin/JS (브라우저) window.performance.now() 또는 Date.now()
Kotlin/Native std::chrono::high_resolution_clock 또는 std::chrono::steady_clock

시간 소스 만들기

다른 시간 소스를 사용하고 싶은 경우가 있어요. 예를 들어 Android에서 System.nanoTime()은 기기가 활성 상태일 때만 시간을 셉니다. 기기가 딥 슬립(deep sleep) 상태에 들어가면 시간 추적을 잃어버리죠. 기기가 딥 슬립 상태일 때도 시간을 추적하려면 SystemClock.elapsedRealtimeNanos()를 사용하는 시간 소스를 만들 수 있어요.

object RealtimeMonotonicTimeSource : AbstractLongTimeSource(DurationUnit.NANOSECONDS) {
    override fun read(): Long = SystemClock.elapsedRealtimeNanos()
}

그런 다음 이 시간 소스를 사용해 시간을 측정할 수 있어요.

fun main() {
    val elapsed: Duration = RealtimeMonotonicTimeSource.measureTime {
        Thread.sleep(100)
    }
    println(elapsed) // e.g. 103 ms
}

더 알아보기