Copy elision과 RVO(Return value optimization)
Copy elision optimization
Copy elision이란 특정 상황에서 Copy와 심지어 Move까지도 생략하는 컴파일러 최적화를 뜻한다. 이전 글에서 Copy elision 최적화가 발생하는 한 가지 상황을 살펴보았다.
class Cat {
public:
void setName(std::string s)
{
name = std::move(s);
}
private:
std::string name;
};
int main()
{
Cat kitty;
// R-value 전달 : 0 copy
kitty.setName("kitty");
return 0;
}
위 코드에서 컴파일러는 컴파일 시간에 setName
의 인자로 R-Value가 넘어오는것을 알수 있다. 그래서 컴파일러는 "kitty"
라는 문자열은 setName
의 s
의 메모리 공간에 바로 저장한다. 그렇게 되어서 Copy는 물론이고 Move 조차 발생하지 않는 것이다. 이는 함수를 반환하는 상황에서도 발생한다.
RVO(Return value optimization)
RVO(Return value optimization)는 함수를 반환할 때 발생하는 Copy elision optimization을 말한다. Copy elision optimization은 앞서 살펴본것 처럼 copy연산과 심지어 move연산 까지도 생략하는 최적화를 뜻 한다.
RVO가 없다면?
RVO를 이해하기 위해서 RVO가 없는 상황을 한번 살펴보자.
std::string getString()
{
std::string s = "abc";
return s;
}
int main()
{
std::string a = getString();
return 0;
}
위와 같은 상황에서 만약 RVO가 없다면 다음과 같이 동작할 것이다.
- 메모리 공간에
a
가 할당됨 - 메모리 공간에
s
가 할당됨 s
에"abc"
대입 후s
반환a
에s
값 복사s
메모리에서 삭제a
메모리에서 삭제
이 과정을 살펴보면 값을 반환하기 위해서 copy가 발생함을 확인할 수 있다. copy가 발생하지 않기 위해서 다음과 같이 코드를 작성할 수도 있을 것이다. RVO 코드를 다음과 같이 수정해 보자.
std::string getString()
{
std::string s = "abc";
return std::move(s);
}
int main()
{
std::string a = getString();
return 0;
}
이번에도 RVO가 없다면 이 코드는 다음과 같이 동작할 것이다.
- 메모리 공간에
a
가 할당 - 메모리 공간에
s
가 할당 s
에"abc"
대입 후s
반환a
에s
값 이동s
메모리에서 삭제a
메모리에서 삭제
이번에는 값을 반환하는데 copy를 하는 대신 소유권을 이전하는 move가 발생했다. std::move
연산을 통해서 기존보다 분명 더 효율 적으로 개선되었다. 하지만 실제 컴파일러에서는 RVO가 동작하기 때문에 이 보다 더 최적화된다.
RVO가 있는 상황
RVO가 개입되는 상황을 살펴보자
std::string getString()
{
std::string s = "abc";
return s; // 0 copy, 0 move
// return std::move(s); // 0 copy, 1 move
}
int main()
{
std::string a = getString();
return 0;
}
RVO가 개입되는 상황에서 위 코드는 아래와 같이 동작한다.
- 메모리 공간에
a
가 할당 a
공간을s
로 간주.s
( ==a
) 에"abc"
대입.
이것이 가능한 이유는 getString
함수가 call stack에 올라갈 때 값이 반환될 주소인 a
의 주소가 함께 올라간다. 그리고 컴파일 타임에 컴파일러는 s
가 곧 a
에 대입될 것을 알고 있다. 그래서 지역변수 s
를 위해 새로운 메모리 공간을 할당하는 것이 아니라, call stack에 올라온 a
의 주소를 s
의 주소인 것 마냥 사용하기 때문에 copy가 일어날 일도 없고, move도 일어나지 않는 것이다. 이렇게 RVO가 개입될 경우 std::move(s)
로 반환하는 것보다 더 효율적이게 동작한다. 이렇게 RVO가 개입하기 때문에 값을 반환할 때는 그냥 return by value로 코드를 작성하는 것이 가장 성능이 좋다.