C# - 인라인 메서드(inline methods)
지인 한 분이 아래의 글이 뭘 설명하는지 잘 모르겠다고 ^^ 질문을 하셨습니다.
.NET Core Best Practices
;
https://www.nilebits.com/blog/2022/04/net-core-best-practices/
By passing arguments, reducing jumps, and restoring registers, inline methods improve app performance. Remember that the JIT (just-in-time) compiler will not inline one method that contains a throw statement. To fix it, create a static helper process that includes a throw statement.
사실 C/C++ 언어를 해보신 분들에게는 자연스러운 듯한 문장인데 아무래도 C#과 같은 VM 언어만을 다뤄본 분들에게는 어려울 수 있을 듯합니다.
간단하게 풀어볼까요? 이를 위해 위의 문장을 2개로 나누고,
[1]
By passing arguments, reducing jumps, and restoring registers, inline methods improve app performance.
[2]
Remember that the JIT (just-in-time) compiler will not inline one method that contains a throw statement. To fix it, create a static helper process that includes a throw statement.
그중에서 "[1]"에 해당하는 부분을 다음의 코드를 곁들여 설명해 보겠습니다.
int x = 0;
int result = Increment(x);
Console.WriteLine(result);
int Increment(int x)
{
return (x + 1);
}
보는 바와 같이 Increment 메서드를 호출하고 있는데요, 메서드로 구현은 되었지만 코드 내용이 간단하다는 특징이 있습니다. 이런 경우 최적화 과정에서 컴파일러(닷넷의 경우 JIT 컴파일러)에 의해 다음과 같이 함수 호출이 아닌, 말 그대로 호출 측으로 인라인 되는 것도 가능합니다.
int x = 0;
int result = x + 1;
Console.WriteLine(result);
즉, 컴파일러의 재량에 의해 전체적인 코드 수행에는 논리적인 오류 없이 코드를 inline 시키는 것입니다. 그렇다면 컴파일러는 왜? 이런 고생을 사서 하는 것일까요? ^^
사실 함수 호출은 어찌 보면 꽤나 비용이 드는 작업입니다. 왜냐하면, 호출자와 피호출자 측에서는 다음과 같은 작업을 요구하기 때문입니다.
[호출자]
인자를 전달하기 위해 스택 또는 레지스터에 push
[피호출자]
스택 프레임, non-volatile 레지스터 백업 등의 prologue 코드
...본 코드 수행...
return 전, 스택 프레임 및 non-volatile 레지스터 등의 복원을 수행하는 epilogue 코드
일반적인 경우라면 Increment 메서드 호출은 저런 코드 수행을 모두 동반해도 문제가 없지만, 만약 반복이 많은 루프 문 내에서 사용되는 경우라면 인라인 메서드의 사용만으로 반복문 수행의 성능을 크게 높일 수 있다는 장점이 있습니다.
이것으로 "[1]"번 부분은 이해하시겠죠? ^^ 자, 그럼 "[2]"번 문장으로 가볼까요?
[2]
Remember that the JIT (just-in-time) compiler will not inline one method that contains a throw statement. To fix it, create a static helper process that includes a throw statement.
컴파일러의 인라인 처리가 최적화의 일환으로 수행은 되지만, 그렇다고 모든 유형의 메서드를 인라인 시킬 수 있는 것입니다. 몇몇 제약이 있는데요, 그중 하나가 바로 "throw" 문을 포함하는 메서드는 인라인 시킬 수 없다는 것입니다. 가령 Increment 메서드를 이렇게 바꾸면,
int Increment(int x)
{
if (x < 0)
{
throw new ArgumentException();
}
return (x + 1);
}
이제 JIT 컴파일러는 Increment 메서드를 throw 문의 포함으로 인해 인라인 시킬 수 없습니다. 대신, 아래와 같이 throw 문을 직접 포함하지 않는 호출로 바꾸면,
static int Increment(int x)
{
if (x < 0)
{
ThrowArgumentException();
}
return (x + 1);
}
static int ThrowArgumentException()
{
throw new ArgumentException();
}
다시, Increment의 내용을 인라인 시키는 것이 가능하다는 것입니다. (그런데, 정말 위의 설명이 사실일까요? 이에 대해서는
다음번 글에서 알아보겠습니다. ^^)
참고로, 인라인 메서드의 좋은 예가 바로 C# 프로퍼티 구문입니다.
C# - 프로퍼티로 정의하면 필드보다 느릴까요?
; https://www.sysnet.pe.kr/2/0/1545
잘 아시는 것처럼, get/set 구문은 결국 메서드로 처리됩니다. 하지만, JIT 컴파일러는 get/set 메서드의 코드가 간결한 경우 인라인 처리를 해버리기 때문에 메서드 호출에 대한 부하가 없습니다. 그러니, 안심하고 마음껏 (필드가 아닌) 프로퍼티로 정의해도 됩니다. ^^
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]