[WWDC24] Explore swift performance - Low-level principles #42
jcrescent61
started this conversation in
WWDC
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Low-level principles
low-level 성능의 4가지 기본 원칙에 대해서 알아보자.
Funtion calls
함수 호출에 연관된 비용은 4가지이다. 개발자가 그중 3가지 작업을 하게된다. 먼저 호출할 때 인수를 설정한다.
그리고 호출하는 함수의 주소를 알아내야 한다. 또한 함수의 로컬 상태를 저장할 공간을 할당해야한다.
4번째 작업은 개발자가 진행하지 않는다. 이 모든 작업이 최적화에 제약을 걸 수 있다. 호출자에서도, 호출하는 함수에서도 그럴 수 있다. 이렇게 4가지 비용이다.
먼저 인수 전달에 대해 살펴보죠 이 비용은 2개 수준에서 발생한다. 가장 낮은 수준에서는 호출을 할 때 호출 규칙에 따라 인수를 올바른 위치에 배치해야 한다. 최신 프로세서에서는 보통 이 비용을 레지스터 이름을 바꿔 숨길 수 있으므로 실질적으로 큰 영향을 주지는 않는다.
하지만 더 높은 수준에서는 함수의 소유권 규칙을 충족하기 위해 컴파일러가 값을 복사해야 할 수도 있다 이는 프로파일의 호출자 또는 호출되는 함수에서 소유권의 추가 획득과 해제로 나타나고는 한다 이 부분은 나중에 다시 �설명할것이다.
다음 두 비용인 함수 결정과 최적화에 미치는 영향은 모두 같은 이유에서 발생한다. 컴파일 타임에 어떤 함수를 호출하는지 정확히 알고 있는가?
알고 있다면 호출은 정적 디스패치를 사용하는 것이고 아니라면 동적 디스패치를 사용하는 것이다. 정적 디스패치는 더 효율적이고 프로세서 수준에서 더 빠르게 작동하기도 하지만 무엇보다 중요한 것은 컴파일 타임에 다양하게 인라인 처리나 제네릭 구체화처럼 상당한 최적화가 이루어진다는 점이다. 컴파일러가 함수 정의를 알 수 있다면 말이다. 하지만 동적 디스패치는 다형성과 같은 강력한 추상화 기능을 제공한다.
Swift에서는 특정 종류의 호출만 동적 디스패치를 사용하며 호출 대상의 정의에서 이를 확인할 수 있습니다
이 예에서는 프로토콜 유형의 값을 업데이트하는 호출이 있습니다 이 호출의 유형은 메서드가 선언된 장소에 따라 달라집니다
프로토콜의 메인 바디 안에 선언된 경우 프로토콜 요구사항이며 호출은 동적 디스패치를 사용합니다
하지만 프로토콜 확장 안에서 선언되면 호출은 정적 디스패치를 사용합니다 이는 의미론적으로도 성능면에서도 아주 중요한 차이가 있습니다
함수 호출에 관한 마지막 비용은 로컬 상태 저장을 위한 메모리 할당입니다 함수가 실행되려면 메모리가 필요합니다 일반적인 동기 함수이므로 이 메모리를 C 스택에 할당한다. C 스택에 공간을 할당하려면 스택 포인터에서 할당할 공간만큼 빼면 도니다.
이를 컴파일하면 어셈블리 코드의 함수 시작과 끝 부분에서 스택 포인터를 조작한다.
함수에 진입하면 스택 포인터가 C 스택을 가리키고 있다.
먼저 스택 포인터에서 값을 빼면서 시작합니다.
어셈블리 코드에서 208바이트만큼 빼고 있다. 이렇게 하면 기존에 CallFrame이라고 부르던 공간이 할당되며 함수가 실행될 공간이 마련된다. 이제 함수 본문을 실행할 수 있다.
반환하기 직전에 스택 포인터에 다시 208바이트를 더해
이전에 할당한 메모리를 할당 해제한다.
CallFrame은 C 구조체 같은 레이아웃으로 생각할 수 있다. 이상적으로는 함수의 모든 로컬 상태가 CallFrame의 필드가 된다 CallFrame에 모든 것을 넣는 게 가장 좋은 이유는 컴파일러가 함수 시작 시 항상 스택 포인터에서 값을 빼 공간을 확보하기 때문이다 반환 주소와 같은 중요한 정보를 저장할 공간이 필요하기 때문이다. 더 큰 상수를 빼더라도 더 많은 시간이 걸리지는 않으니 함수에 메모리가 필요한 경우 CallFrame의 일부로 할당하는 게 가장 비용이 적게 든다.
Memory allocation
전통적으로 메모리는 세 종류로 구분된다. 물론 컴퓨터 입장에서는 결국 동일한 RAM 상의 메모리이다. 하지만 프로그램에서 우리는 서로 다른 패턴으로 할당하고 사용한다. 이러한 점은 운영 체제에 중요한 사항이며 성능 면에서도 중요하다.
글로벌 메모리는 프로그램 로드 시 할당되고 초기화된다. 비용은 거의 없다시피 한다. 글로벌 메모리의 커다란 단점은 고정된 크기의 메모리를 사용하는 특정 패턴에서만 사용 가능하며, 프로그램을 실행하는 동안 메모리가 유지된다는 점이다. 이러한 특성은 글로벌 변수와 정적 멤버 변수에는 적합하지만 그 외의 경우에는 적합하지 않다.
스택 할당의 예시로 CallFrame에 대해 살펴보았다. 글로벌 메모리 처럼 스택 메모리도 비용이 아주 적지만 특정 패턴에만 사용할 수 있다. 스택 메모리의 경우 메모리의 범위가 한정되어야 한다. 현재 함수의 특정 지점부터는 해당 메모리가 더 이상 사용되지 않도록 해야한다. 일반적인 로컬 변수에 적합하다.
마지막 종류의 메모리는 힙이다.
Beta Was this translation helpful? Give feedback.
All reactions