RVO, RNVO 객체 반환 최적화 기법
목차
- RVO와 NRVO의 개념
- RVO와 NRVO의 차이점
- 이점, 유의점
- 결론: 컴파일러가 알아서 해주나요? 코드 치는 사람은 무엇을 하면 되나요?
C++는 강력한 성능을 위해 사용하는 언어로써 다양한 최적화를 통해 더 나은 성능을 꾀어낼 수 있다. 이러한 점 덕분에 C/C++가 흥미로운 언어인 것 같다.(매우 멋짐!)
RVO와 NRVO의 개념
RVO(Return Value Optimization)와 NRVO (Named Return Value Optimization)는 함수가 객체를 반환할 때 발생할 수 있는 불필요한 복사와 이동을 줄여주는 중요한 최적화 기법이다.
RVO (Return Value Optimization)란?
RVO는 함수가 임시 객체를 반환할 때 발생할 수 있는 불필요한 복사를 피하는 최적화인다. 컴파일러는 반환값을 위한 메모리를 미리 할당할 수 있고, 그 메모리를 직접 사용하여 복사를 방지한다.
Foo createFoo()
{
return Foo(); // RVO 적용
}
위와 같은 상황에서 RVO가 적용되면 반환 과정에서 임시객체를 생성하지 않고 반환되는 메모리에 직접 객체 생성이 된다. (객체의 복사가 생략되는 아름다운 상황)
NRVO (Named Return Value Optimization)란?
NRVO는 lvalue로서 명명된 지역변수를 반환할 때 발생할 수 있는 불필요한 복사를 피하는 최적화이다. RVO와 비슷하지만, NRVO는 지역변수로 생성된(변수명을 가진!) 객체를 반환할 때 적용된다.
Foo createFoo()
{
Foo var;
// f에 대한 여러 작업
return var; // NRVO 적용
}
위의 코드에서 var
는 지역변수로 생성된 객체이다. NRVO가 적용되면 var
를 반환할 때 복사가 생략되고, 반환이 최적화된다.
RVO와 NRVO의 차이점
RVO는 리턴값에 바로 객체를 생성하는 임시객체 반환
의 상황에서 최적화가 되고, NRVO는 반환하는 함수의 지역변수 객체를 반환
하는 상황에서 최적화가 된다.
NRVO에서 N은 Named의 약자로 지역변수로 만들어진, 변수명을 가진, 명명되었다는 것을 의미한다는 점에서 차이를 기억하면 좋을 것 같다.
이점, 유의점
당연히 성능이고, C/C++를 사용하는 본질적인 이점이 될 수 있다. 이러한 최적화는 객체 생성이 무수히 많을 수 밖에 없는 상황에서 사용하면 좋다. 그렇다고 RVO/NRVO를 믿고 많은 객체를 생성하는 코드를 작성하는 것은 바람직하지 않다. 최적화가 되어있다 하더라도 객체 생성은 오버헤드가 발생할 수 있기 때문에 객체 생성을 최대한 방지하고 이미 생성된 객체를 재활용하는 등의 고민이 우선되어야할 것 같다. RVO/NRVO의 이점을 통해 성능을 개선하는 상황은 결국 많은 객체를 생성하는 상황일테니 객체 생성을 줄일 수 있는 방법을 먼저 고민하는 것이 적절할 것이다.
결론: 컴파일러가 알아서 해주나요? 코드 치는 사람은 무엇을 하면 되나요?
문법 레벨에서 직접 RVO/NRVO를 처리할 수 없다. 대신 컴파일러가 알아서 최적화를 해준다. 그렇기 때문에 개발자는 컴파일러가 최적화할 수 있는 상황을 만들어주는 것이 최선이다.
다음과 같은 상황에서 RVO/NRVO가 작동되지 않을 수 있다.
- 반환되는 객체가 함수 내에서 조건문에 따라 생성될 때.
- 반환되는 객체가 다른 함수의 반환값일 때.
- 반환되는 객체가 참조 또는 포인터일 때.
예시는 아래와 같다
1. 조건문에 따라 반환되는 객체가 다른 경우
Foo createConditionalFoo(bool flag)
{
if (flag)
{
return Foo(); // 임시 객체
}
else
{
Foo f;
return f; // 명명된 객체
}
}
2. 여러 반환 지점이 있는 경우
Foo createFooWithMultipleReturns(bool flag)
{
if (flag)
{
return Foo(); // 첫 번째 반환 지점
}
Foo f;
return f; // 두 번째 반환 지점
}
3. 객체가 다른 함수의 반환값일 때
Foo createFooFromAnotherFunction()
{
return anotherFunctionReturningFoo(); // 다른 함수의 반환값
}
4. 반환값이 참조나 포인터일 때
const Foo& createFooReturningReference()
{
static Foo f;
return f; // 참조 반환
}
5. 복잡한 객체 생성 로직
Foo createComplexFoo(int value)
{
if (value > 0)
{
Foo f1;
// f1에 대한 여러 작업
return f1;
}
else
{
Foo f2;
// f2에 대한 여러 작업
return f2;
}
}
따라서 무수히 많은 객체가 생성될 것 같은 상황에서는 RVO/NRVO가 적용될 수 있도록 개발자가 고려를 해주면 좋다.
여담
C++위원회는 이러한 기능을 1996년에 발표하고 VS2005에 포함시키게 되었다.
Leave a comment