C ++ 11/14는 단지 C ++ 98 코드를 컴파일 할 때에도 성능을 향상시킬 수 있다고 주장합니다. 정당화는 보통 rvalue 생성자가 자동으로 생성되거나 STL의 일부이므로 이동 시맨틱 라인을 따릅니다. 이제 이러한 사례가 실제로 RVO 또는 유사한 컴파일러 최적화로 이미 처리되었는지 궁금합니다.
내 질문은 수정하지 않고 새로운 언어 기능을 지원하는 컴파일러를 사용하여 더 빠르게 실행되는 C ++ 98 코드의 실제 예를 나에게 줄 수 있는지 여부입니다. 필자는 표준 준수 컴파일러가 복사 제거를 수행 할 필요가 없으며 이동 시맨틱이 속도를 가져올 수있는 이유를 이해하지만 병리학 적 사례를보고 싶습니다.
편집 : 명확하게 말하면, 새로운 컴파일러가 이전 컴파일러보다 빠른지 묻지 않고 컴파일러 플래그에 -std = c ++ 14를 추가하는 코드가 있으면 더 빨리 실행됩니다 (복사본은 피하십시오) 이동 의미론 외에 다른 것을 생각 해낼 수 있습니다.
답변
C ++ 11로 C ++ 03 컴파일러를 다시 컴파일하면 구현 품질과 실질적으로 관련이없는 무한한 성능 향상이 발생할 수있는 5 가지 일반적인 범주를 알고 있습니다. 이것들은 모두 이동 의미의 변형입니다.
std::vector
재 할당
struct bar{
std::vector<int> data;
};
std::vector<bar> foo(1);
foo.back().data.push_back(3);
foo.reserve(10); // two allocations and a delete occur in C++03
마다 foo
의 버퍼가 03 C ++로 재 할당은 모든 복사 vector
에 bar
.
C ++ 11에서는 대신 bar::data
기본적으로 무료 인 s 를 이동합니다 .
이 경우 std
컨테이너 내부의 최적화에 의존합니다 vector
. 아래의 모든 경우에 std
컨테이너를 사용하는 move
것은 컴파일러를 업그레이드 할 때 C ++ 11에서 “자동”으로 효율적인 의미 를 갖는 C ++ 객체이기 때문 입니다. std
컨테이너 를 포함하는 컨테이너를 차단하지 않는 개체 도 자동으로 개선 된 move
생성자를 상속합니다 .
NRVO 실패
NRVO (이름 반환 값 최적화)가 실패하면 C ++ 03에서는 복사시, C ++ 11에서는 이동시 돌아갑니다. NRVO의 실패는 쉽다 :
std::vector<int> foo(int count){
std::vector<int> v; // oops
if (count<=0) return std::vector<int>();
v.reserve(count);
for(int i=0;i<count;++i)
v.push_back(i);
return v;
}
또는:
std::vector<int> foo(bool which) {
std::vector<int> a, b;
// do work, filling a and b, using the other for calculations
if (which)
return a;
else
return b;
}
우리는 세 가지 값, 즉 반환 값과 함수 내에서 다른 두 값을 가지고 있습니다. Elision을 사용하면 함수 내의 값이 반환 값과 ‘병합’되지만 서로는 병합되지 않습니다. 서로 병합하지 않고 반환 값과 병합 할 수 없습니다.
기본적인 문제는 NRVO 제거가 취약하고 return
사이트 근처가 아닌 변경 사항이있는 코드가 갑자기 진단을받지 않고 해당 지점에서 성능이 크게 저하 될 수 있다는 것입니다. 대부분의 NRVO 실패 사례에서 C ++ 11은로 끝나고 move
C ++ 03은 복사본으로 끝난다.
함수 인수 반환
여기에서도 제거가 불가능합니다 :
std::set<int> func(std::set<int> in){
return in;
}
C ++ 11에서는 이것이 저렴합니다. C ++ 03에서는 복사를 피할 방법이 없습니다. 매개 변수 및 리턴 값의 수명 및 위치는 호출 코드에 의해 관리되므로 함수에 대한 인수는 리턴 값으로 제거 할 수 없습니다.
그러나 C ++ 11은 서로 이동할 수 있습니다. 장난감이 적은 예에서는 무언가가 수행 될 수 있습니다 set
.
push_back
또는 insert
마지막으로 컨테이너로의 제거는 발생하지 않지만 C ++ 11은 rvalue 이동 삽입 연산자를 오버로드하여 사본을 저장합니다.
struct whatever {
std::string data;
int count;
whatever( std::string d, int c ):data(d), count(c) {}
};
std::vector<whatever> v;
v.push_back( whatever("some long string goes here", 3) );
C ++ 03에서 임시 whatever
가 생성 된 다음 vector에 복사됩니다 v
. std::string
각각 동일한 데이터를 갖는 2 개의 버퍼가 할당되고 하나는 버려집니다.
C ++ 11에서는 임시 whatever
가 생성됩니다. whatever&&
push_back
과부하는 move
벡터에 그 임시이야 v
. 하나의 std::string
버퍼가 할당되어 벡터로 이동합니다. 공란 std::string
은 버립니다.
할당
아래 @ Jarod42의 답변에서 도난당했습니다.
배정은 배제 할 수 없지만 이사 할 수는 있습니다.
std::set<int> some_function();
std::set<int> some_value;
// code
some_value = some_function();
여기서 제거 some_function
할 후보를 리턴하지만 오브젝트를 직접 구성하는 데 사용되지 않으므로 제거 할 수 없습니다. C ++ 03에서 위의 결과는 임시 내용이에 복사됩니다 some_value
. C ++ 11에서는 some_value
기본적으로 무료 로로 이동 합니다.
위의 모든 효과를 얻으려면 이동 생성자와 할당을 합성하는 컴파일러가 필요합니다.
MSVC 2013은 std
컨테이너에 이동 생성자를 구현 하지만 사용자 유형에 이동 생성자를 합성하지는 않습니다.
따라서 std::vector
s 및 이와 유사한 것을 포함하는 유형 은 MSVC2013에서 그러한 개선을 얻지 못하지만 MSVC2015에서 유형을 가져 오기 시작합니다.
clang과 gcc는 오랫동안 암시 적 이동 생성자를 구현했습니다. 인텔의 2013 컴파일러는 통과하는 경우 암시 적 생성 생성자를 지원합니다 -Qoption,cpp,--gen_move_operations
(MSVC2013과의 상호 호환성을 위해 기본적으로 수행하지는 않음).
답변
당신이 같은 것을 가지고 있다면 :
std::vector<int> foo(); // function declaration.
std::vector<int> v;
// some code
v = foo();
C ++ 03에는 사본이 있지만 C ++ 11에는 이동 할당이 있습니다. 이 경우 무료 최적화가 가능합니다.