C ++ 11의 '자동'을 사용하여 성능을 향상시킬 수 있습니까?


230

autoC ++ 11 의 유형이 정확성과 유지 관리 성을 향상시키는 이유를 알 수 있습니다 . 나는 또한 성능을 향상시킬 수 있다는 것을 읽었 지만 (허브 스퍼트에 의해 거의 항상 자동 ), 나는 좋은 설명을 그리워합니다.

  • 어떻게 auto성능 을 향상시킬 수 있습니까?
  • 누구나 모범을 보여줄 수 있습니까?

5
가젯에서 위젯으로의 실수로 암시적인 변환을 피하는 방법에 대해 설명 하는 herbsutter.com/2013/06/13/… 를 참조하십시오 . 일반적인 문제는 아닙니다.
Jonathan Wakely

42
성능 향상으로 "의도하지 않고 비관적 일 가능성이 줄어 듭니다"라는 말을 받아들입니까?
5gon12eder

1
미래에 코드 정리의 성능 만 가능할 것입니다.
Croll

우리는 짧은 대답이 필요합니다. 'noobish'실수를 예방할 수 있습니다. C ++에는 학습 곡선이 없으므로 결국 그것을 만들지 않는 사람들을 죽입니다.
Alec Teal

답변:


309

auto자동 암시 적 변환피함으로써 성능을 향상시킬 수 있습니다 . 내가 찾은 예는 다음과 같습니다.

std::map<Key, Val> m;
// ...

for (std::pair<Key, Val> const& item : m) {
    // do stuff
}

버그가 보입니까? 여기서 우리는 const 참조로 맵의 모든 항목을 우아하게 취하고 새로운 범위-범위 표현식을 사용하여 의도를 명확하게 나타내지 만 실제로 모든 요소를 복사 하고 있다고 생각 합니다. 때문입니다 std::map<Key, Val>::value_type입니다 std::pair<const Key, Val>,하지 std::pair<Key, Val>. 따라서 우리가 (내재적으로) 가지고있을 때 :

std::pair<Key, Val> const& item = *iter;

기존 객체에 대한 참조를 가져 와서 그대로 두는 대신 유형 변환을 수행해야합니다. 다음과 같이 암시 적 변환이 가능한 한 다른 유형의 객체 (또는 임시)에 대한 const 참조를 사용할 수 있습니다.

int const& i = 2.0; // perfectly OK

형식 변환은 a const Key를 a 로 변환 할 수있는 것과 같은 이유로 허용되는 암시 적 변환 Key이지만,이를 허용하려면 새 형식의 임시를 구성해야합니다. 따라서 효과적으로 루프가 수행합니다.

std::pair<Key, Val> __tmp = *iter;       // construct a temporary of the correct type
std::pair<Key, Val> const& item = __tmp; // then, take a reference to it

(물론 실제로 __tmp객체 가 없으며 단지 설명을 위해 존재합니다. 실제로 명명되지 않은 임시 파일은 item수명 기간 동안 바인딩됩니다 ).

그냥 다음으로 변경 :

for (auto const& item : m) {
    // do stuff
}

방금 우리에게 많은 사본을 저장했습니다. 이제 참조 된 유형이 이니셜 라이저 유형과 일치하므로 임시 또는 변환이 필요하지 않으므로 직접 참조 할 수 있습니다.


19
컴파일러가 행복하게 복사본을 만드는 대신의 치료에 시도에 대해 불평하는 이유 @Barry는 설명 할 수 std::pair<const Key, Val> const &가 AS를 std::pair<Key, Val> const &? C ++ 11을 처음 접했을 때 어떻게 범위를 auto넓히고 재생 하는지 확실하지 않습니다 .
Agop

@Barry 설명해 주셔서 감사합니다. 그것이 내가 놓친 부분입니다-어떤 이유로 든, 당신은 일시적인 것에 대한 지속적인 참조를 가질 수 없다고 생각했습니다. 그러나 물론 당신은 할 수 있습니다-그것의 범위의 끝에서 존재하는 것을 멈출 것입니다.
Agop

@ barry 나는 당신을 얻지 만 문제는 auto성능 향상 을 사용 하는 모든 이유를 다루는 대답이 없다는 것입니다 . 그래서 나는 그것을 내 자신의 말로 쓸 것이다.
Yakk-Adam Nevraumont

38
여전히 이것이 " auto성능 향상 "의 증거라고 생각하지 않습니다 . " auto성능을 파괴하는 프로그래머 실수를 방지하는 데 도움이 되는"예일뿐입니다 . 나는이 둘 사이에 미묘하지만 중요한 차이점이 있다고 주장한다. 여전히 +1입니다.
궤도에서 가벼움

70

때문에 auto초기화 식의 추론 유형, 반군 유형 변환은 없습니다. 템플릿 알고리즘과 함께 사용하면 유형을 직접 구성 할 때보 다, 특히 이름을 지정할 수없는 유형의 표현식을 처리 할 때보 다 더 직접적인 계산을 얻을 수 있습니다.

일반적인 예는 다음과 같이 사용됩니다 std::function.

std::function<bool(T, T)> cmp1 = std::bind(f, _2, 10, _1);  // bad
auto cmp2 = std::bind(f, _2, 10, _1);                       // good
auto cmp3 = [](T a, T b){ return f(b, 10, a); };            // also good

std::stable_partition(begin(x), end(x), cmp?);

cmp2하고 cmp3, 당신이 만들 경우 반면에, 비교 호출을 인라인 할 수있는 전체 알고리즘 std::function객체가 아니라 통화가 인라인 될 수없는,하지만 당신은 함수 래퍼의 유형 - 삭제 내부 다형성 조회를 통과해야합니다.

이 주제의 다른 변형은 다음과 같이 말할 수 있다는 것입니다.

auto && f = MakeAThing();

이것은 항상 함수 호출 표현식의 값에 바인딩 된 참조이며 추가 오브젝트를 구성하지 않습니다. 반환 된 값의 유형을 모르는 경우 다음과 같은 방법으로 새 ​​객체 (임시로)를 생성해야 할 수 있습니다 T && f = MakeAThing(). (또한, auto &&리턴 타입이 움직일 수없고 리턴 값이 prvalue 일 때에도 작동합니다.)


따라서 이것이 "사용하지 않는 유형 삭제"이유 auto입니다. 다른 변형은 "우연한 사본을 피하는"것이지만 꾸미기가 필요합니다. 왜 auto단순히 유형을 입력하는 것보다 빠른 속도를 제공합니까? (정답은 "유형이 잘못되어 조용히 변환된다"고 생각합니다.) Barry의 대답에 대한 잘 설명되지 않은 예입니다. 즉, 유형 삭제를 피하기위한 자동과 실수로 변환되는 자동 유형 오류를 피하는 자동의 두 가지 기본 경우가 있습니다. 둘 다 런타임 비용이 있습니다.
Yakk-Adam Nevraumont

2
"통화를 인라인 할 수 없을뿐만 아니라"-왜 그럴까요? 데이터 관련 전문 경우 분석 흐름 후에는 원칙적으로 뭔가 방지에 통화가 devirtualized되고 있음을 의미합니까 std::bind, std::function그리고 std::stable_partition모든 인라인 된? 아니면 실제로 C ++ 컴파일러가 엉망을 분류 할만 큼 공격적으로 인라인하지 않습니까?
Steve Jessop

@SteveJessop : 대부분 후자- std::function생성자를 거친 후에 는 특히 작은 함수 최적화를 사용하여 실제 호출을 통해 보는 것이 매우 복잡합니다 (따라서 실제로 가상화를 원하지 않습니다). 물론 원칙적으로 모든 것은 마치 ...
Kerrek SB

41

두 가지 범주가 있습니다.

auto유형 삭제를 피할 수 있습니다. 람다와 같은 이름없는 유형과 거의 이름없는 유형 (결과 std::bind또는 다른 표현 템플릿과 같은)이 있습니다.

없으면 auto데이터를 지우고 입력해야합니다 std::function. 유형 삭제에는 비용이 있습니다.

std::function<void()> task1 = []{std::cout << "hello";};
auto task2 = []{std::cout << " world\n";};

task1가능한 힙 할당, 인라인 어려움 및 가상 함수 테이블 호출 오버 헤드와 같은 유형 삭제 오버 헤드가 있습니다. task2없습니다. 람다 유형 삭제없이 저장 하려면 자동 또는 다른 유형의 유형 공제가 필요합니다. 다른 유형은 너무 복잡하여 실제로 필요합니다.

둘째, 유형이 잘못 될 수 있습니다. 경우에 따라 잘못된 유형이 완벽하게 작동하지만 사본이 발생할 수 있습니다.

Foo const& f = expression();

경우 컴파일 expression()반환 Bar const&또는 Bar심지어 Bar&어디 Foo에서 구성 할 수있다 Bar. 임시 Foo가 생성 된 다음에 바인딩되며 f수명이 연장 될 때까지 연장 f됩니다.

프로그래머는 Bar const& f복사를 의도하거나 의도하지 않았지만 복사는 사본에 관계없이 작성됩니다.

가장 일반적인 예는의 유형이지만이 유형 *std::map<A,B>::const_iteratorstd::pair<A const, B> const&그렇지 std::pair<A,B> const&않지만 오류는 자동으로 성능을 요하는 오류 범주입니다. 당신은을 구성 할 수 std::pair<A, B>A로부터 std::pair<const A, B>. (지도를 편집하는 것은 좋지 않기 때문에지도의 키는 const입니다)

@Barry와 @KerrekSB는이 두 가지 원칙을 먼저 답했습니다. 이것은 단순히 예제 중심이 아닌 문제를 목표로하는 표현으로 한 가지 답변으로 두 가지 문제를 강조하려는 시도입니다.


9

기존의 세 가지 답변 auto"무의식적으로 비관적으로 비관 할 가능성을 줄여 주는 "도움말 을 사용하여 "성능을 향상시키는"예를 제공합니다.

동전에는 뒤집힌면이 있습니다. auto기본 객체를 반환하지 않는 연산자가있는 객체와 함께 사용하면 잘못된 (아직 컴파일 가능하고 실행 가능한) 코드가 발생할 수 있습니다. 예를 들어, 이 문제는 사용하는 방법을 묻는 auto, 고유치 라이브러리를 사용하여 다른 (잘못된) 결과 준 즉, 다음 줄을

const auto    resAuto    = Ha + Vector3(0.,0.,j * 2.567);
const Vector3 resVector3 = Ha + Vector3(0.,0.,j * 2.567);

std::cout << "resAuto = " << resAuto <<std::endl;
std::cout << "resVector3 = " << resVector3 <<std::endl;

다른 출력 결과. 분명히 이것은 대부분 Eigens 게으른 평가 때문이지만 코드는 (라이브러리) 사용자에게 투명해야합니다.

여기서 성능은 크게 영향을받지 않지만 auto의도하지 않은 비관 화를 피하기 위해 사용 하는 것은 조기 최적화 또는 적어도 잘못된 것으로 분류 될 수 있습니다.


1
반대 질문을 추가했습니다 : stackoverflow.com/questions/38415831/…
Leon
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.