최신 C ++로 무료 성능을 얻을 수 있습니까?


205

C ++ 11/14는 단지 C ++ 98 코드를 컴파일 할 때에도 성능을 향상시킬 수 있다고 주장합니다. 정당화는 보통 rvalue 생성자가 자동으로 생성되거나 STL의 일부이므로 이동 시맨틱 라인을 따릅니다. 이제 이러한 사례가 실제로 RVO 또는 유사한 컴파일러 최적화로 이미 처리되었는지 궁금합니다.

내 질문은 수정하지 않고 새로운 언어 기능을 지원하는 컴파일러를 사용하여 더 빠르게 실행되는 C ++ 98 코드의 실제 예를 나에게 줄 수 있는지 여부입니다. 필자는 표준 준수 컴파일러가 복사 제거를 수행 할 필요가 없으며 이동 시맨틱이 속도를 가져올 수있는 이유를 이해하지만 병리학 적 사례를보고 싶습니다.

편집 : 명확하게 말하면, 새로운 컴파일러가 이전 컴파일러보다 빠른지 묻지 않고 컴파일러 플래그에 -std = c ++ 14를 추가하는 코드가 있으면 더 빨리 실행됩니다 (복사본은 피하십시오) 이동 의미론 외에 다른 것을 생각 해낼 수 있습니다.


3
복사 생성자와 반환 값 최적화는 복사 생성자를 사용하여 새 객체를 생성 할 때 수행됩니다. 그러나 복사 할당 연산자에는 복사 제거가 없습니다 (컴파일러는 임시가 아닌 이미 구성된 객체로 수행 할 작업을 알지 못하므로 어떻게 할 수 있습니까). 따라서이 경우 이동 할당 연산자를 사용할 수 있으므로 C ++ 11 / 14가 크게 승리합니다. 그러나 귀하의 질문에 대해서는 C ++ 11/14 컴파일러로 컴파일하면 C ++ 98 코드가 더 빠르지 않을 것이라고 생각합니다. 컴파일러가 최신이기 때문에 더 빠를 수도 있습니다.
vsoftco

27
또한 C ++ 11 / 14에서는 기본 라이브러리가 가능한 경우 내부적으로 이동 의미를 사용하므로 표준 라이브러리를 사용하는 코드는 C ++ 98과 완전히 호환되도록해도 잠재적으로 더 빠릅니다. 따라서 C ++ 98 및 C ++ 11 / 14에서 동일하게 보이는 코드는 벡터, 목록 등의 표준 라이브러리 객체를 사용하고 의미를 이동시킬 때마다 후자의 경우 더 빠를 것입니다.
vsoftco

1
@vsoftco, 그것은 내가 암시하는 일종의 상황이지만 예제를 만들 수 없었습니다. 복사 생성자를 정의해야 할 때 기억하는 것으로부터 이동 생성자가 자동으로 생성되지 않으므로 RVO가 항상 작동하는 매우 간단한 수업입니다. STL 컨테이너와 관련하여 예외가 될 수 있습니다. rvalue 생성자는 라이브러리 구현자가 생성합니다 (이동을 사용하기 위해 코드에서 아무것도 변경하지 않아도 됨).
대형

복사 생성자를 갖지 않기 위해 클래스가 단순 할 필요는 없습니다. C ++은 가치 의미론을 바탕으로 번창하며 복사 생성자, 할당 연산자, 소멸자 등은 예외입니다.
sp2danny

1
@Eric 링크 주셔서 감사합니다, 그것은 재미있었습니다. 그러나 빠르게 살펴보면 속도 이점은 주로 std::move생성자 를 추가 하고 이동하는 것 (기존 코드를 수정해야 함) 에서 비롯된 것 같습니다 . 내 질문과 실제로 관련된 유일한 것은 "재 컴파일만으로 즉각적인 속도 이점을 얻을 수 있습니다"라는 문장이었습니다. ). 나는 몇 가지 예를 요구했다. 슬라이드를 잘못 읽으면 알려주십시오.

답변:


221

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 ++로 재 할당은 모든 복사 vectorbar.

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은로 끝나고 moveC ++ 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::vectors 및 이와 유사한 것을 포함하는 유형 은 MSVC2013에서 그러한 개선을 얻지 못하지만 MSVC2015에서 유형을 가져 오기 시작합니다.

clang과 gcc는 오랫동안 암시 적 이동 생성자를 구현했습니다. 인텔의 2013 컴파일러는 통과하는 경우 암시 적 생성 생성자를 지원합니다 -Qoption,cpp,--gen_move_operations(MSVC2013과의 상호 호환성을 위해 기본적으로 수행하지는 않음).


1
@alarge 예. 그러나 이동 생성자가 복사 생성자보다 여러 배 더 효율적이기 위해서는 일반적으로 리소스를 복사하는 대신 이동해야합니다. 자신의 이동 생성자를 작성하지 않고 C ++ 03 프로그램을 다시 컴파일하지 않으면 std라이브러리 컨테이너가 모두 move"무료"생성자 로 업데이트되며 (차단하지 않은 경우) 해당 객체를 사용하는 생성자 ( 그리고 상기 물체들)은 많은 상황에서 자유 이동 구성을 얻기 시작할 것이다. 이러한 상황 중 많은 부분이 C ++ 03에서 제거에 의해 다루어집니다. 전부는 아닙니다.
Yakk-Adam Nevraumont

5
따라서 반환되는 다른 이름을 가진 객체의 수명이 겹치지 않기 때문에 이론적으로는 RVO가 가능하기 때문에 최적화가 잘못 구현되었습니다.
Ben Voigt

2
@alarge 수명이 겹치는 두 객체를 서로 1/3로 분리 할 수있는 경우와 같이 제거가 실패하는 곳이 있습니다. 그런 다음 C ++ 11에서 이동이 필요하고 C ++ 03으로 복사하십시오 (있는 경우 무시). 실제로 배설물은 깨지기 쉽습니다. std위 의 컨테이너는 주로 C ++ 03을 다시 컴파일 할 때 C ++ 11에서 '무료'로 제공되는 복사 유형으로 저렴하게 이동할 수 있기 때문에 주로 사용 됩니다. 는 vector::resize예외이다 :이 사용 move11 ++ C이다.
Yakk-Adam Nevraumont

27
이동 의미론 인 일반 범주는 1 개, 특수한 경우는 5 개입니다.
Johannes Schaub-litb

3
@sebro 나는 "프로그램이 많은 1000 킬로바이트 할당을 할당하지 않고 대신 포인터를 움직인다"고 생각하지 않는다는 것을 이해한다. 당신은 시간 결과를 원한다. Microbenchmarks는 기본적으로 성능 저하를 증명하는 것보다 성능 향상의 증거가 아닙니다. 실제 작업 프로파일 링으로 프로파일 링되는 다양한 산업에서 100여 개의 실제 응용 프로그램 중 일부가 실제로 입증되지는 않습니다. "무료 성능"에 대한 모호한 주장을했고 C ++ 03 및 C ++ 11에서 프로그램 동작의 차이점에 대해 구체적으로 설명했습니다.
Yakk-Adam Nevraumont

46

당신이 같은 것을 가지고 있다면 :

std::vector<int> foo(); // function declaration.
std::vector<int> v;

// some code

v = foo();

C ++ 03에는 사본이 있지만 C ++ 11에는 이동 할당이 있습니다. 이 경우 무료 최적화가 가능합니다.


4
@ Yak : 할당에서 복사 제거가 어떻게 발생합니까?
Jarod42

2
@ Jarod42 나는 또한 왼쪽이 이미 구성되어 있고 오른쪽에서 리소스를 훔친 후 컴파일러가 "오래된"데이터로 무엇을해야하는지 알 수있는 합리적인 방법이 없기 때문에 할당에서 복사 제거가 불가능하다고 생각합니다. 손 쪽. 그러나 내가 틀렸을 수도 있습니다. 나는 한 번, 영원히 답을 찾고 싶습니다. 객체가 "새롭고"이전 데이터로 수행 할 작업을 결정하는 데 아무런 문제가 없으므로 생성자를 복사 할 때 복사 제거가 의미가 있습니다. 내가 아는 한, 유일한 예외는 다음과 같습니다. "할당은 as-if 규칙을 기반으로 만 제거 할 수 있습니다"
vsoftco

4
좋은 C ++ 03 코드는 이미이 경우를 통해 이동했습니다.foo().swap(v);
Ben Voigt

@BenVoigt는 확실하지만 모든 코드가 최적화 된 것은 아니며, 이런 일이 발생하는 모든 지점에 도달하기 쉬운 것은 아닙니다.
Yakk-Adam Nevraumont

복사 ellision은 @BenVoigt와 같이 과제에서 작동 할 수 있습니다. 더 나은 용어는 RVO (반환 값 최적화)이며 foo ()가 이와 같이 구현 된 경우에만 작동합니다.
DrumM
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.