RVO, RNVO 객체 반환 최적화 기법

목차

  1. RVO와 NRVO의 개념
  2. RVO와 NRVO의 차이점
  3. 이점, 유의점
  4. 결론: 컴파일러가 알아서 해주나요? 코드 치는 사람은 무엇을 하면 되나요?

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에 포함시키게 되었다.

Categories:

Updated:

Leave a comment