포스트

Swift에서의 Type Inference(타입 추론)의 작동 원리와 효율적 사용을 위한 전략

타입 추론과 타입 어노테이션

1. 타입 추론(Type Inference)

컴파일러가 주변 컨텍스트에서 세부 정보를 파악할 수 있을 때 코드에서 명시적 타입 정보를 생략할 수 있음

1
let name = "molu"
  • 작동 과정
    • 1단계 : 어휘분석(Lexical analysis)

    컴파일러는 입력 파일 바이트를 숫자 및 문자열과 같은 단위로 분할, 공백과 주석을 버리는 어휘 분석을 수행함. 예를 들어 위 예제를 let, name, =, ", molu, "로 분할함

    • 2단계 : 구문 분석(Syntax analysis)

    컴파일러는 swift 문법에 기초한 abstract syntax tree 를 생성함 관련하여 자료는 여기를 참고

    • 3단계 : 의미론적 분석(Semantic analysis) = 컴파일러 경고 및 오류 발행 단계

      molu에 대한 추가적인 정보가 없기 때문에 이 단계에서 타입추론이 일어남. 저 표현식에서 변수 선언의 오른쪽이 string이라서 string이어야 함을 추론함

    • 그 이후

      컴파일러는 중간 코드를 생성한 다음 그 코드를 최적화하고, 최종적으로 어셈블리 코드를 생성함. 그 이후 컴파일러 외부의 tool chain은 마지막 실행파일을 생성하는 연결 단계로 이어짐

  • 장점
    • swift 언어의 구조적 특정중 설계에 의한 안정성
    • 직접 타입을 명시해주지 않아도 된다는 간편함
  • 단점
    • 타입 추론은 컴파일러가 타입을 추론하는 과정에서 타입 어노테이션보다 시간이 조금 더 걸림
    • 초기값이 없는 경우, 타입을 유추할 수 없어서 사용할 수 없음
    • 컴파일러가 초기값을 보고 유추할 때, Character/String, Double/Float 등 애매하면 더 큰 범위의 자료형으로 냅다 지정해버림

2. 타입 어노테이션(Type Annotation)

변수나 상수를 선언할 때 그 타입을 명시적으로 선언해 줌으로써 어떤 타입의 값이 저장될 것인지를 컴파일러에 직접 알려주는 문법

1
let name: String
  • 장점
    • 컴파일 시간?
    • 타입 추론으로 원하는 자료형을 얻지 못할 때 사용할 수 있음
    • 초기값 없어도 사용할 수 있음

컴파일 시간에 대하여

1. 타입 어노테이션이 빠르다?

두 가지 방법의 컴파일 시간을 비교했을 때 타입명시가 빠르다는 실험 결과가 나와있음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var inference: Double = 0
for _ in 0..<10 {
    let inferenceTime = measureTime {
        for _ in 0..<100000 {
            let _ = 1
        }
    }
    inference += inferenceTime
}

var annotation: Double = 0
for _ in 0..<10 {
    let annotationTime = measureTime {
        for _ in 0..<100000 {
            let _: Int = 1
        }
    }
    annotation += annotationTime
}

2. 타입 추론이 빠르다?

위 처럼 타입 어노테이션이 추론하는 과정이 생략되기 때문에 컴파일 시간이 더 빠르다는 것이 보통의 이론이지만, 컴파일러의 성능 향상으로 타입 추론의 컴파일 속도가 더 빠르다는 실험결과도 있음

1
2
3
4
let a = "hello, world!" // type is inferred
let b = String("hello, world!") // type is inferred from String(...) and then passed to the root (the constant b)
let c: String = .init("hello, world!") // type inference is not required
let d: String = "hello, world!" // type inference is not required
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Benchmark #1: xcrun swiftc -typecheck a.swift
  Time (mean ± σ):     175.7 ms ±   3.5 ms    [User: 82.9 ms, System: 81.9 ms]
  Range (min  max):   171.0 ms  182.8 ms    16 runs

Benchmark #1: xcrun swiftc -typecheck b.swift
  Time (mean ± σ):     224.8 ms ±   2.8 ms    [User: 131.1 ms, System: 81.7 ms]
  Range (min  max):   220.2 ms  228.2 ms    13 runs

Benchmark #1: xcrun swiftc -typecheck c.swift
  Time (mean ± σ):     672.3 ms ±   8.0 ms    [User: 568.3 ms, System: 93.7 ms]
  Range (min  max):   662.4 ms  685.1 ms    10 runs

Benchmark #1: xcrun swiftc -typecheck d.swift
  Time (mean ± σ):     213.3 ms ±   2.0 ms    [User: 119.8 ms, System: 81.6 ms]
  Range (min  max):   210.2 ms  216.5 ms    13 runs

위처럼 가장 첫번째 경우(타입추론) 이 가장 빠른 컴파일 속도를 보임.

  • 타입추론은 컴파일러가 타입을 추론하는 과정이 추가되기 때문에 컴파일 시간이 늘어남
  • 하지만 타입을 명시하게 되면 컴파일러가 명시된 타입과 초기값을 비교하는 작업이 추가되어 컴파일 시간이 늘어날 수 있음
  • 애초에 타입 명시와 타입 추론의 속도 차이 자체도 밀리세컨드 단위 차이로 미세함
  • 업데이트마다 컴파일러의 성능이 어떤 지표에 대하여 오락가락하는 것으로 보임

🤔 명시적 타입 선언과 타입 추론 중 어느 경우가 더 바람직한가

컴파일러가 업데이트 될 때마다 성능이 오락가락하는 것으로 보임.. 애초에 비교가 무의미할 정도로 차이가 미미하기 때문에 성능을 비교하기 보다는 다른 측면의 장점(ex. 가독성 등)을 비교하여 적재적소에 사용하는 것이 현명해보임

참조

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.

인기 태그