Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

게임 개발 메모장

[ C++ ] RVO(Return Value Optimization), NRVO(Named Return Value Optimization) 본문

언리얼 엔진/C++

[ C++ ] RVO(Return Value Optimization), NRVO(Named Return Value Optimization)

Dev_Moses 2023. 6. 1. 15:18

C/C++를 사용하는 이유 중 하나로는 빠른 속도와 커스터마이징 가능한 자원관리 등의 최적화를 위해서 사용할 것이다. 이러한 부분에서 코드를 개발하면서, 컴파일러단에서 부터 코드를 최적화 시켜서 더욱 바르게 실행 할 수 있도록 하려는 노력들이 많이 있다.

 

이처럼 C/C++ 에서는 내가 원하는대로 코드를 작성하더라도 컴파일러에 의해

최적화 시킬 여지가 있으면 그러한 부분은 컴파일러가 최적화 시켜서 돌아가도록 만들어준다.

 

RVO(Return Value Optimization)

#include <iostream>

class Yoo 
{

public:
    Yoo(const std::string& name) 
    {
        this->name = name;
        std::cout << "This is " << name << " constructor." << std::endl;
    }

    ~Yoo() 
    {
        std::cout << "This is " << this->name << " desstructor." << std::endl;
    }

    Yoo(const Yoo& rhs) 
    {
        this->name = rhs.name;
        std::cout << "This is " << rhs.name << " copy constructor." << std::endl;
    }

    Yoo& operator=(const Yoo& rhs) 
    {
        std::cout << "This is " << this->name << " copy operator." << std::endl;
        return *this;
    }

private:
    std::string name;
};

//NRVO
Yoo MakeYoo(const std::string& name) 
{
    Yoo Yoo(name);
    return Yoo;
}

//RVO
Yoo MakeYoo2(const std::string& name) 
{
    return Yoo(name);
}

int main() 
{
    auto Yoo  = MakeYoo("Yoo1");
    auto Yoo2 = MakeYoo2("Yoo2");

    std::cout << "---end---" << std::endl;
    return 0;
}

 

 

위의 Yoo의 결과 객체가 NRVO의 예시이고 Yoo2의 결과 객체가 NRVO의 대표적인 예시이다.

 

그렇다면 호출 순서를 알아보자.

 

1. main의 첫줄에서 MakeYoo함수로 인자가 전달된다.

2. MakeYoo(const std::string&) 함수 내부에서 기본 생성자가 불린다.

3. return 될 때 내부에 생성된 Yoo 객체의 소멸자가 불리고 Yoo 객체의 복사 생성자가 불릴것이다.

4. main의 두번째줄에서 MakeYoo2함수로 인자가 전달된다

5. MakeYoo2(const std::string&) 함수에서 return에서 기본 생성자가 호출된다.

6. main의 Yoo2에 복사생성자가 불린다.

 

한번 출력된 결과를 살펴보자

This is Yoo1 constructor.
This is Yoo1 copy constructor.
This is Yoo1 desstructor.
This is Yoo2 constructor.
---end---
This is Yoo2 desstructor.
This is Yoo1 desstructor.

 

 

위와 같이 복사생성자가 불린 것을 확인 할 수 있다. 하지만 foo2의 경우는 복사생성자가 호출되지 않았다.

 

위의 코드는 Debug 모드에서 실행 한 상태이다.

 

그렇기 때문에 최적화 수준에 따라 다르게 동작한다.

 

이 코드를 Release 모드에서 돌리면 어떻게 될까?

 

This is Yoo1 constructor.
This is Yoo2 constructor.
---end---
This is Yoo2 desstructor.
This is Yoo1 desstructor.

 

 

Debug 모드일 때와 다른 출력 결과를 보여주고 있다.

그 이유는 Release모드에서는 기본적으로 컴파일러가 최적화를 해주기 때문이다. 

 

MakeYoo라는 함수에서 생성된 Yoo는 return 시켜

main의 Yoo = 다음의 우측값으로 넣을 필요 없이

바로 Yoo에 대입하면 된다는것을 컴파일러가 인지하고 최적화를 시킨것이다.

 

RVO는 컴파일러 최적화와 관계없이 무조건 사용되기 때문에 Debug, Release

모두에서 RVO가 적용된 결과를 보여준다.

 

NRVO는 RVO에 속하는 종류로써 예전에는 이러한 MakeFoo 라는 함수 내부에 foo라는 이름을 가진 객체는 return 시켜도 최적화 시키지 않았다. 그러나 사람들이 이름을 가지더라도 최적화 시키자고 하였고 이를 NRVO(Named Return Value Optimization)이라고 부르게 되었다. 그래서 ISO/ANSI C++ 위원회에서 1996년 RVO와 NRVO의 최적화 가능을 발표하고, Visual studio 2005에 포함시키게 되었다.

 

이처럼 컴파일러는 자신도 모르는 사이에 상황에 맞게 RVO, NRVO 뿐만 아니라 다양한 최적화 기법들을 이용해서 적절한 코드로 최적화 시키는 작업을 컴파일러단에서 진행하고 있다.

 

드디어 왜 클래스를 반환할 때 임시객체를 대입해야 하는지 이해했다!

근데 릴리즈모드에서는 하나 안하나 차이가 없기 때문에 굳이 신경쓸 필요는 없다.