High-performance systems in Swift - 2019 dotSwift

rbw·2023년 1월 1일
0

TIL

목록 보기
65/98

참조

https://www.youtube.com/watch?v=iLDldae64xE

위 영상을 보고 정리한 글, 영어 잘 못하니가 몇몇 내용은 틀릴수있다는 .(점) ~

Overhead

  • Heap allocations
  • Reference count operations
  • Copies
  • ...

느려지는 원인들은 위와 같다

예제는 HTTP 요청 코드로 잡음

public struct HTTPRequest {
    public var method: HTTPMethod
    public var target: String
    public var version: HTTPVersion
    public var headers: [(String, String)]
    public var body: [UInt8]?
    public var trailers: [(String, String)]?
}

public enum HTTPMethod: String {
    case GET
    case POST
    case DELETE
    case PUT
    // ...
}

public struct HTTPVersion {
    var major: Int
    var minor: Int
}

여기서의 HTTP 요청은 값 유형입니다

let swift = HTTPRequest(method: .GET,
                        target: "/hello/dotSwift",
                        ...)

var ai = swift
ai.target = "/hello/dotAI"

XCTAssertEqual(swift.target, "/hello/dotSwift") // pass ~

위 코드를 보면 aiswift를 넣고 다른 값을 주어도 값 타입이라 테스트가 통과하는걸 볼 수 있습니다.

Passing values around

// HTTP 요청을 변환하는 함수
// 이 친구에 대한 성능을 이야기 해보려고 한다고 함
public func transform(_ httpRequest: HTTPRequest) -> HTTPRequest {
    return httpRequest
}

_ = transform(httpRequest) // 52ns 시간을 재는중 ~

// 비교를 하려면 기준이 필요함
public func transform(_ value: Int) -> Int {
    return value
}

_ = transform(someInt) // 1ns ! 위의 52ns는 꽤나 느리다는걸 알 수 있다

What happens at runtime ?

정수를 넘기는 함수 부터 살펴보겠습니다.

위 숫자 23을 함수로 넘긴다면 이를 복사해야 하는 행위가 일어납니다. 올바른 프로세스 레지스터를 입력하고 함수로 이동합니다. 다음 그림 처럼 수행이되고 끝이 납니다.

이제 위에서 살펴본 HTTP 요청에서는 어떨까요 ?

HTTPRequest 구조체를 넘기는 함수는 사실 다음과 같습니다.

이것만 봐도 시간이 더 오래걸리는 이유가 나옵니다.

내부를 좀 더 살펴보면 다음 사진과 같습니다.

그리고 더 안 좋은 상황은 문자열과 배열 같은 친구들은 힙에 할당이 되는 부분임.

회색 선은 힙에 할당한 부분을 참조하기 위해 필요한 주소값들임

이제 이 친구들도 함수에 전달하고 함수는 복사를 합니다. 그럼 무슨일이 일어나는지 다음 사진과 같이 보겠습니당.

문자열과 배열을 함수에 전달하면 이는 참조 카운트도 하나 증가한다는 의미가 됩니다. 위 사진에서는 4번의 참조 카운팅이 더 증가가 되었습니다. 이러한 이유 때문에 오버헤드가 더 일어난다고 볼 수 있슴니다.

구조체 대신 클래스로 HTTPRequest로 만들고 함수에 전달을 한다면, 클래스 하나의 참조 카운트만 늘어서 속도는 빠릅니다. (영상에서는 15ns 라고 나옴 구조체였을 때는 52ns 여씀)

이러한 이유 때문에 클래스가 구조체보다 빠르다는 이야기가 나왔다고 함. 하지만 이는 사실이 아님

클래스로 이를 구성하지 않는 이유

HTTPRequest가 클래스로 만들었다면 프로그램에서 값 의미론(value semantics, 라고 하는데 아마 하나만 존재해야하는 유일성? 맞나 이런거라고 생각했슴다)의 가치를 잃을 수 있는 점 때문에 더 빠르게 전달이 가능합니다.

하지만 이는 프로그램에 있어서 중요합니다.

맨 위에서 본 ai, swift 예제에서, 클래스로 구성했다면 같이 변경이 되며, 테스트는 실패하게 됩니다. 이는 좋지 x

Value(struct) backed by class

여기서 우리는 값 체계처럼 사용되지만 런타임에서는 클래스처럼 보이게 하려고합니다. (이러면 장점만 가져가서 좋을둣함)

요약을 하자면

  • 구조체 내부에 private class _Storage를 만듭니다. (밑줄은 딱히 의미는 없다고함 좀 더 읽기 쉽게 했다고 하는듯함)
  • 이를 보고있는 프로퍼티도 하나 만듭니다. private var _storage: _Storage
  • 하지만 아직 같은것을 바라보고 있는 문제가 있으므로, 이를 해결해야함
  • isKnownUniquelyReferenced(&self._stoarge) 이 메서드는 내가 유일하게 참조하고 있는지를 불리언 값으로 알려줍니다.
  • 이를 활용해서 유일하게 생성을 할 수 있습니다 !

슬라이드 사진 보면서 요약 내용 생각하면 이해가 더 쉽게 될거라고 생각함다.

만약 false 라면 위 사진의 왼쪽 상황이 되게끔 복사를 진행해야 합니다. 그래야 참조 체계가 아닌 값 체계처럼 동작합니다.

이런식(구조체 내부 클래스를 사용한 위 방식)으로 완성하면 성능은 클래스로 실험했던 15ns 와 동일하였다고 함.

마무리하며

클래스 vs 구조체에서 뭘 써야할지에 대해서는 성능의 이유는 x. 의미 체계를 따르는게 맞다고 하심. 내가 참조 체계가 필요하면 클래스 ~ 값 체계다 구조체 ~ 라는 느낌 인듯하다.

그리고 위에서 설명한 Cow-box를 모든 구조체에 할 필요는 없다고 함. 적절한 상황에 맞는 구조체에만 사용하는것을 추천하였다. 사용하기 전에 측정을 해보라고 함.


이번 세션도 유익했다고 생각한다 dotSwift 좋네 ~ 좀 더 구조체와 클래스에 대한 차이를 알게 되는 부분이 아니였나 싶음. 그리고 참조 카운팅의 영향도 ~

profile
hi there 👋

0개의 댓글