컴파일러 최적화에 의존하는 코드를 작성하는 것은 나쁜 습관입니까?


99

나는 C ++을 배우고 있었고 종종 함수 내에서 생성 된 함수에서 큰 객체를 반환해야합니다. 참조로 전달하고 포인터를 반환하고 참조 유형 솔루션을 반환한다는 것을 알고 있지만 C ++ 컴파일러 (및 C ++ 표준)는 반환 값 최적화를 허용하여 메모리를 통해 이러한 큰 객체를 복사하지 않도록합니다. 모든 시간과 메모리를 절약 할 수 있습니다.

이제 객체가 값으로 명시 적으로 반환되면 구문이 훨씬 명확하다고 생각합니다. 컴파일러는 일반적으로 RVO를 사용하고 프로세스를보다 효율적으로 만듭니다. 이 최적화에 의존하는 것은 나쁜 습관입니까? 그것은 사용자에게 코드를 명확하고 읽기 쉽게 만듭니다. 이것은 매우 중요하지만 컴파일러가 RVO 기회를 잡을 것이라고 가정해야합니까?

이것이 미세 최적화입니까, 아니면 코드를 디자인 할 때 명심해야 할 것이 있습니까?


7
수정에 답하기 위해, 그것은 마이크로 최적화입니다. 왜냐하면 당신이 얻은 것을 나노초로 벤치마킹하려고 시도하더라도 거의 볼 수 없기 때문입니다. 나머지는 C ++에서 너무 썩어서 왜 작동하지 않는지에 대한 엄격한 대답을 제공합니다. 동적 할당이 필요할 때 새 / 포인터 / 참조를 사용하는 경우가있을 수 있습니다.
Walfrat

4
@Walfrat 객체가 메가 바이트 단위로 상당히 큰 경우에도? 내가 해결하는 문제의 특성으로 인해 배열이 엄청나게 커질 수 있습니다.
Matt

6
@Matt하지 않습니다. 이에 대한 참조 / 포인터가 존재합니다. 컴파일러 최적화는 프로그램을 빌드 할 때 프로그래머가 고려해야 할 사항을 뛰어 넘는 것으로 여겨지지만, 종종 두 세계가 겹치는 횟수가 있습니다.
Neil

5
@ 매트 C / 커널에서 10 년 이상의 경험을 가진 개발자를 필요로하는 매우 구체적인 작업을 수행하지 않는 한 낮은 하드웨어 상호 작용은 필요하지 않습니다. 매우 구체적인 내용에 속한다고 생각되면 게시물을 편집하고 응용 프로그램에서 수행해야 할 작업에 대한 정확한 설명을 추가하십시오 (실시간? 무거운 수학 계산? ...)
Walfrat

37
C ++의 (N) RVO 의 특별한 경우 에,이 최적화에 의존하는 것은 완벽하게 유효합니다. 이는 현대 컴파일러가 이미 수행하고있는 상황에서 C ++ 17 표준이 특별히 요구 하기 때문입니다.
Caleth

답변:


130

고용 최소한의 놀라움 원리를 .

이 코드를 사용하는 사람은 누구입니까? 3 년 후에도 같은 일이 당신이하는 일에 놀라지 않을 것입니까?

그런 다음 계속하십시오.

다른 모든 경우에는 표준 방식을 사용하십시오. 그렇지 않으면, 당신과 당신의 동료들은 버그를 찾기가 어려워 질 것입니다.

예를 들어, 동료가 내 코드로 인해 오류가 발생했다고 불평했습니다. 그는 컴파일러 설정에서 단락 부울 평가를 해제했습니다. 나는 거의 그를 때렸다.


88
@Neil 그게 내 요점입니다. 모두 단락 평가에 의존합니다. 그리고 당신은 그것에 대해 두 번 생각할 필요가 없습니다, 그것은 켜져 있어야합니다. 사실상의 표준입니다. 예, 변경할 수는 있지만 변경해서는 안됩니다.
Pieter B

49
"언어가 작동하는 방식을 바꾸었고 더러운 썩은 코드 가 깨졌습니다! Arghh!" 와. 때리는 것이 적절할 것입니다. 동료를 Zen 교육에 보내십시오.

109
@PieterB C C ++ 언어 사양이 단락 평가를 보장한다고 확신합니다 . 따라서 이것은 사실상의 표준이 아니라 표준입니다. 그것 없이는 더 이상 C / C ++을 사용하지 않지만 의심 스럽습니다. : P
marcelm

47
참고로 여기의 표준 방법은 값으로 반환하는 것입니다.
DeadMG

28
@ dan04 예, Delphi에있었습니다. 얘들 아, 내가 본 요점에 관한 예에서 잡히지 마십시오. 다른 사람이하는 놀라운 일을하지 마십시오.
Pieter B

81

이 특별한 경우에는 반드시 값으로 반환하십시오.

  • RVO와 NRVO는 C ++ 03 모드에서도 괜찮은 컴파일러에 의해 잘 알려져 있고 잘 알려진 강력한 최적화입니다.

  • 이동 의미론은 (N) RVO가 발생하지 않은 경우 객체가 기능에서 벗어나도록합니다. 개체가 (같은 내부적으로 동적 데이터를 사용하는 경우 그에만 유용 std::vector하지 않습니다)하지만,이 경우 그 정말로 경우해야 한다는 스택 오버 플로우를하는 것은 큰 자동 객체와 위험 - 큰.

  • C ++ 17은 RVO를 시행 합니다. 따라서 걱정하지 마십시오. 컴파일러가 최신 상태가되면 완전히 사라질 것입니다.

마지막으로 포인터를 반환하도록 추가 동적 할당을 강요하거나 결과 유형을 기본값으로 구성 가능하도록 강제하여 출력 매개 변수를 추론 할 수 있습니다. 있다.

이해하기 쉬운 코드를 작성하고 이해하기 쉬운 코드를 올바르게 최적화 한 컴파일러 작성자에게 감사의 말을 전하십시오.


9
1990-ish의 Borland Turbo C ++ 3.0이 RVO를 처리하는 방법 을 재미있게 살펴 보십시오 . 스포일러 : 기본적으로 잘 작동합니다.
nwp

9
여기서 핵심은 임의의 컴파일러 특정 최적화 또는 "언급되지 않은 기능"이 아니라 여러 버전의 C ++ 표준에서 기술적으로 선택 사항이지만 업계에서 크게 밀려 났으며 거의 ​​모든 주요 컴파일러가 수행 한 것입니다. 아주 오랜 시간.

7
이 최적화는 원하는만큼 강력하지 않습니다. 그렇습니다. 가장 확실한 경우에는 다소 신뢰할 만하지 만 gcc의 버그 질라를 찾아 보면 놓칠 수없는 간신 한 경우가 많이 있습니다.
Marc Glisse

62

이제 객체가 값으로 명시 적으로 반환되면 구문이 훨씬 명확하다고 생각합니다. 컴파일러는 일반적으로 RVO를 사용하고 프로세스를보다 효율적으로 만듭니다. 이 최적화에 의존하는 것은 나쁜 습관입니까? 그것은 사용자에게 코드를 명확하고 읽기 쉽게 만듭니다. 이것은 매우 중요하지만 컴파일러가 RVO 기회를 잡을 것이라고 가정해야합니까?

이것은 작고 인신 매매가 적은 블로그에서 읽은 작고 귀엽고 미세한 최적화가 아니며 사용에 대해 영리하고 우월한 느낌을줍니다.

C ++ 11 이후 RVO는 이 코드를 작성 하는 표준 방법 입니다. 표준에서 언급 된 블로그에서 언급되고 토론에서 언급되는 것이 일반적이고, 예상되고, 가르치고, 구현되지 않으면 컴파일러 버그로보고 될 것입니다. C ++ 17에서 언어는 한 단계 더 나아가 특정 시나리오에서 복제 제거를 요구합니다.

이 최적화에 전적으로 의존해야합니다.

뿐만 아니라 값으로 반환하면 참조로 반환되는 코드보다 훨씬 쉽게 코드를 읽고 관리 할 수 ​​있습니다. 가치 의미론은 그 자체로 더 많은 최적화 기회를 가져올 수있는 강력한 것입니다.


3
감사합니다. 이것은 의미가 있으며 위에서 언급 한 "최소한 놀랍게도 원칙"과 일치합니다. 코드를 매우 명확하고 이해하기 쉽게 만들고 포인터 shenanigans와 혼동하기가 더 어려워집니다.
Matt

3
@Matt이 답변을 찬성 한 이유 중 하나는 "가치 의미론"을 언급하기 때문입니다. C ++ (및 일반적인 프로그래밍)에 대한 경험이 많을수록 가치 의미론이 특정 객체에 대해 변경 가능하고 동일한 객체 (예 : "공유 가변성"의 예). 이러한 상황이 발생하면 영향을받는 개체를 (스마트) 포인터를 통해 공유해야합니다.
rwong

16

작성한 코드의 정확성은 최적화에 의존 해서는 안됩니다 . 스펙에서 사용하는 C ++ "가상 머신"에서 실행될 때 올바른 결과를 출력해야합니다.

그러나 당신이 말하는 것은 더 효율적인 종류의 질문입니다. RVO 최적화 컴파일러로 최적화하면 코드가 더 잘 실행됩니다. 다른 답변에서 지적한 모든 이유로 괜찮습니다.

그러나이 최적화가 필요한 경우 (예 : 복사 생성자가 실제로 코드를 실패하게하는 경우) 이제 컴파일러의 변덕에 있습니다.

내 자신의 연습에서 이것의 가장 좋은 예는 꼬리 호출 최적화라고 생각합니다.

   int sillyAdd(int a, int b)
   {
      if (b == 0)
          return a;
      return sillyAdd(a + 1, b - 1);
   }

어리석은 예이지만 함수의 끝에서 함수가 재귀 적으로 호출되는 꼬리 호출을 보여줍니다. C ++ 가상 머신은이 코드가 올바르게 작동 함을 보여 주지만, 처음에 그러한 추가 루틴을 작성하는 것을 귀찮게 했는지에 대해 약간의 혼란을 초래할 수 있습니다 . 그러나 실제 C ++ 구현에서는 스택이 있으며 공간이 제한되어 있습니다. Pedantically 수행하는 경우,이 기능은 b + 1스택 프레임을 스택에 추가 할 때 스택 프레임에 추가해야합니다. 계산하고 싶다면 sillyAdd(5, 7)큰 문제가 아닙니다. 계산하고 싶다면 sillyAdd(0, 1000000000)실제로 StackOverflow (및 좋은 종류는 아님) 를 일으키는 데 어려움을 겪을 수 있습니다 .

그러나 마지막 리턴 라인에 도달하면 현재 스택 프레임의 모든 작업이 완료된 것을 볼 수 있습니다. 우리는 실제로 그것을 유지할 필요가 없습니다. 테일 콜 최적화를 통해 다음 기능에 기존 스택 프레임을 "재사용"할 수 있습니다. 이런 식으로 우리는 1 개가 아닌 1 개의 스택 프레임 만 있으면 b+1됩니다. (우리는 여전히 엉뚱한 덧셈과 뺄셈을 모두 수행해야하지만 더 많은 공간을 차지하지 않습니다.) 실제로 최적화는 코드를 다음과 같이 바꿉니다.

   int sillyAdd(int a, int b)
   {
      begin:
      if (b == 0)
          return a;
      // return sillyAdd(a + 1, b - 1);
      a = a + 1;
      b = b - 1;
      goto begin;  
   }

일부 언어에서는 사양에서 테일 콜 최적화가 명시 적으로 필요합니다. C ++은 그중 하나 가 아닙니다 . 사례별로 가지 않는 한이 꼬리 호출 최적화 기회를 인식하기 위해 C ++ 컴파일러에 의존 할 수 없습니다. 내 Visual Studio 버전에서 릴리스 버전은 테일 콜 최적화를 수행하지만 디버그 버전은 의도적으로 설계되지 않았습니다.

따라서 내가 계산할 수있는 것에 의존 하는 것은 나쁘다 sillyAdd(0, 1000000000).


2
이것은 흥미로운 코너 케이스이지만 첫 번째 단락의 규칙으로 일반화 할 수는 없다고 생각합니다. 소형 장치 용 프로그램이 있다고 가정 해보십시오. 컴파일러의 크기 감소 최적화를 사용하는 경우에만로드됩니다. 그렇지 않은가요? 내 유일한 올바른 선택은 어셈블러에서 다시 작성하는 것입니다. 특히 재 작성이 문제를 해결하기 위해 최적화 프로그램과 동일한 작업을 수행하는 경우 특히 그렇습니다.
sdenham

5
@ sdenham 나는 논쟁에 약간의 여지가 있다고 생각합니다. 더 이상 "C ++"을 작성하지 않고 "WindRiver C ++ 컴파일러 버전 3.4.1"을 작성하는 경우에는 논리를 볼 수 있습니다. 그러나 일반적으로 사양에 따라 제대로 작동하지 않는 내용을 작성하는 경우 시나리오가 매우 다릅니다. Boost 라이브러리에는 이와 같은 코드가 있지만 항상 #ifdef블록에 넣고 표준 호환 해결 방법을 사용할 수 있습니다.
Cort Ammon

4
두 번째 코드 블록의 오타 b = b + 1입니까?
stib

2
표준 문서에서 사용되는 용어가 아니기 때문에 "C ++ 가상 머신"의 의미를 설명 할 수 있습니다. 나는 당신이 C ++의 실행 모델에 대해 이야기하고 있다고 생각 하지만 완전히 확실하지는 않습니다. 당신의 용어는 완전히 다른 것과 관련된 "바이트 코드 가상 머신"과 현혹 적으로 유사합니다.
Toby Speight

1
@supercat Scala에는 명시적인 꼬리 재귀 구문도 있습니다. C ++은 그 자체의 야수이지만 꼬리 재귀는 비 기능적 언어의 경우 단오 적이며 기능적 언어의 경우 필수적이며 꼬리 꼬리 재귀 구문을 명시 적으로 표현하는 것이 합리적인 작은 언어 세트를 남겨 둡니다. 말 그대로 재귀를 루프로 변환하고 명시적인 돌연변이는 많은 언어에서 더 나은 옵션입니다.
prosfilaes

8

실제로 C ++ 프로그램은 일부 컴파일러 최적화를 기대하고 있습니다.

표준 컨테이너 구현 의 표준 헤더를 특히 살펴보십시오 . 함께 GCC , 당신은 전처리 형태 (질문을 할 수있어 g++ -C -E) 및합니다 (GIMPLE 내부 표현 g++ -fdump-tree-gimple또는 Gimple SSA -fdump-tree-ssa컨테이너를 사용하여 대부분의 소스 파일) (기술적 번역 단위). 으로 수행 된 최적화의 양에 놀랄 것 g++ -O2입니다. 따라서 컨테이너의 구현자는 최적화에 의존합니다 (그리고 대부분의 경우 C ++ 표준 라이브러리의 구현자는 어떤 최적화가 일어날 지 알고 컨테이너 구현을 염두에두고 작성합니다. 때로는 컴파일러에서 최적화 패스를 작성하기도합니다) 표준 C ++ 라이브러리에 필요한 기능을 처리하십시오).

실제로 C ++ 및 표준 컨테이너를 충분히 효율적으로 만드는 것은 컴파일러 최적화입니다. 따라서 당신은 그들에게 의지 할 수 있습니다.

귀하의 질문에 언급 된 RVO 사례도 마찬가지입니다.

C ++ 표준은 가능한 최적화와 잘 작동하도록 공동 설계되었습니다 (특히 새로운 기능을 제안하면서 충분한 최적화를 실험함으로써).

예를 들어 아래 프로그램을 고려하십시오.

#include <algorithm>
#include <vector>

extern "C" bool all_positive(const std::vector<int>& v) {
  return std::all_of(v.begin(), v.end(), [](int x){return x >0;});
}

로 컴파일하십시오 g++ -O3 -fverbose-asm -S. 생성 된 함수가 CALL기계 명령어를 실행하지 않는다는 것을 알게 될 것 입니다. 따라서 대부분의 C ++ 단계 (람다 클로저 구성, 반복 응용 프로그램, 가져 오기 beginend반복자 등)가 최적화되었습니다. 머신 코드에는 루프 만 포함됩니다 (소스 코드에는 명시 적으로 나타나지 않음). 이러한 최적화가 없으면 C ++ 11은 성공하지 못합니다.

부록

( 2017 년 12 월 31 추가 )

참조 CppCon 2017 : 매트 Godbolt는 "어떻게 내 컴파일러는 최근에 나를 위해 행하신? 컴파일러의 뚜껑을 벗기다” 대화.


4

컴파일러를 사용할 때마다 컴퓨터 또는 바이트 코드가 생성된다는 것을 이해합니다. 언어의 사양에 따라 소스 코드를 구현한다는 점을 제외하고는 생성 된 코드가 어떤지에 대해서는 보증하지 않습니다. 이 보증은 사용 된 최적화 수준에 관계없이 동일하므로 일반적으로 한 출력을 다른 출력보다 '올바른'것으로 간주 할 이유가 없습니다.

또한 언어로 지정된 RVO와 같은 경우에는 특히 소스 코드를 더 단순하게 만드는 경우에는 사용하지 않는 것이 좋습니다.

컴파일러가 효율적인 출력을 생성하도록 많은 노력을 기울이고 있으며 이러한 기능을 사용하려는 의도는 분명합니다.

최적화되지 않은 코드를 사용하는 이유가있을 수 있지만 (예 : 디버깅)이 질문에 언급 된 사례는 하나가 아닌 것으로 보입니다 (그리고 최적화 된 경우에만 코드가 실패하고 코드의 특성에 따라 다른 결과가 발생하지 않습니다) 장치를 실행하고 있다면 어딘가에 버그가 있으며 컴파일러에있을 가능성이 없습니다.)


3

다른 사람들이 C ++ 및 RVO에 대한 특정 각도를 잘 다루었다고 생각합니다. 더 일반적인 대답은 다음과 같습니다.

정확성에 관해서는 일반적으로 컴파일러 최적화 또는 컴파일러 특정 동작에 의존해서는 안됩니다. 다행히도, 당신은 이것을하지 않는 것 같습니다.

그것은 성능에 올 때, 당신은 해야 컴파일러 특정 일반적 행동, 특히 컴파일러 최적화에 의존하고 있습니다. 컴파일 된 코드가 언어 사양에 따라 동작하는 한 표준 호환 컴파일러는 원하는 방식으로 코드를 자유롭게 컴파일 할 수 있습니다. 그리고 각 작업의 속도를 지정하는 주류 언어에 대한 사양을 알고 있지 않습니다.


1

컴파일러 최적화는 결과가 아닌 성능에만 영향을 미칩니다. 비 기능적 요구 사항을 충족시키기 위해 컴파일러 최적화에 의존하는 것이 합리적 일뿐만 아니라 종종 한 컴파일러가 다른 컴파일러를 선택하는 이유입니다.

특정 작업 수행 방법 (예 : 인덱스 또는 오버플로 조건)을 결정하는 플래그는 종종 컴파일러 최적화에 집중되어 있지만 그렇게해서는 안됩니다. 계산 결과에 명시 적으로 영향을 미칩니다.

컴파일러 최적화로 인해 다른 결과가 발생하는 경우 이는 컴파일러의 버그 인 버그입니다. 컴파일러의 버그에 의존하는 것은 장기적인 실수입니다. 수정되면 어떻게됩니까?

계산 작동 방식을 변경하는 컴파일러 플래그를 사용하여 문서화해야하지만 필요에 따라 사용해야합니다.


불행히도, 많은 컴파일러 문서는 다양한 모드에서 보장되거나 보장되지 않는 것을 지정하는 데 실패합니다. 또한, "현대"컴파일러 작성자는 프로그래머가 필요로하지 않는 보증의 조합을 잊어 버리는 것 같습니다. x*y>z오버플로 가 발생하는 경우 프로그램이 임의로 0 또는 1을 생성하는 경우 다른 부작용이 없다면 프로그래머는 모든 비용으로 오버플로를 방지하거나 컴파일러가 특정 방식으로 식을 평가하도록 강제해야합니다. 불필요하게 훼손 최적화 대 그 말 ...
supercat

... 컴파일러는 수도 의 여가 로하지만 행동은 x*y(따라서 리프팅 및 일부 오버 플로우의 경우의 동작을 변경 것 강도 감소의 형태를 허용하는) 어떤 임의의 이상 유형의 피연산자를 촉진합니다. 그러나 많은 컴파일러는 프로그래머가 모든 비용으로 오버플로를 방지하거나 오버플로시 컴파일러가 모든 중간 값을 자르도록 요구합니다.
supercat

1

아니.

그것이 제가 항상하는 일입니다. 메모리의 임의의 16 비트 블록에 액세스 해야하는 경우이 작업을 수행합니다

void *ptr = get_pointer();
uint16_t u16;
memcpy(&u16, ptr, sizeof(u16)); // ntohs omitted for simplicity

... 그리고 코드를 최적화하기 위해 가능한 모든 작업을 수행하는 컴파일러에 의존하십시오. 이 코드는 ARM, i386, AMD64 및 실질적으로 모든 단일 아키텍처에서 작동합니다. 이론적으로 최적화되지 않은 컴파일러는 실제로을 호출 memcpy하여 성능이 완전히 저하 될 수 있지만 컴파일러 최적화를 사용하므로 문제가되지 않습니다.

대안을 고려하십시오.

void *ptr = get_pointer();
uint16_t *u16ptr = ptr;
uint16_t u16;
u16 = *u16ptr;  // ntohs omitted for simplicity

정렬 get_pointer()되지 않은 포인터를 반환하는 경우 적절한 정렬이 필요한 시스템에서는이 대체 코드가 작동하지 않습니다 . 또한 대안에 앨리어싱 문제가있을 수 있습니다.

memcpy트릭을 사용할 때 -O2와 -O0의 차이점은 3.2Gbps의 IP 체크섬 성능 대 67Gbps의 IP 체크섬 성능입니다. 차수가 큰 차이!

때로는 컴파일러를 도와야 할 수도 있습니다. 예를 들어, 루프를 풀기 위해 컴파일러에 의존하는 대신 직접 수행 할 수 있습니다. 유명한 더프의 장치 를 구현 하거나 더 깨끗한 방법으로.

컴파일러 최적화에 의존하는 단점은 gdb를 실행하여 코드를 디버깅하면 많은 부분이 최적화되었다는 것을 알 수 있다는 것입니다. 따라서 -O0으로 다시 컴파일해야 할 수도 있습니다. 즉 디버깅 할 때 성능이 완전히 저하됩니다. 컴파일러 최적화의 이점을 고려할 때 이것이 가치가 있다고 생각합니다.

당신이 무엇을 하든지, 당신의 길은 실제로 정의되지 않은 행동이 아닌지 확인하십시오. 16 비트 정수로 일부 임의의 메모리 블록에 액세스하는 것은 앨리어싱 및 정렬 문제로 인해 정의되지 않은 동작입니다.


0

어셈블리를 제외한 모든 것에서 효율적인 코드를 작성하려는 시도는 컴파일러 최적화에 매우 크게 의존합니다. 가장 효율적인 레지스터 할당부터 시작하여 불필요한 스택 유출을 방지하고 최소한 우수하지는 않지만 명령 선택을 합리적으로 방지합니다. 그렇지 않으면 우리는 80 년대로 돌아가서 어디에서나 register힌트를 주었고 함수에서 최소 개수의 변수를 사용하여 구식 C 컴파일러를 돕기 위해 또는 심지어 goto유용한 분기 최적화였던 때 까지 사용했습니다.

최적화 프로그램이 코드를 최적화하는 기능에 의존 할 수 있다고 생각하지 않으면 어셈블리에서 성능에 중요한 실행 경로를 코딩하고 있습니다.

실제로 컴파일러의 성능을 프로파일 링하고 살펴보고, 컴파일러가 보이는 곳을 파악할 수없는 핫스팟이있는 경우 분해하여 가장 잘 정렬 된 최적화가 얼마나 신뢰할 수 있다고 생각하는지 문제입니다. 명백한 최적화를하지 못했습니다.

RVO는 오랫동안 사용되어 왔으며 적어도 매우 복잡한 경우를 제외하고는 컴파일러가 연령대에 안정적으로 적용되는 것입니다. 존재하지 않는 문제를 해결하는 것은 가치가 없습니다.

두려워하지 말고 옵티 마이저에 의존하는 쪽의 오류

반대로, 나는 컴파일러 최적화에 너무 많이 의존하는 것이 잘못되었다고 말하고 싶습니다.이 제안은 고객들 사이에서 효율성, 유지 보수성 및 품질이인지되는 매우 중요한 성능 분야에서 일하는 사람에게서 나옵니다. 하나의 거대한 흐림. 차라리 당신은 옵티 마이저에 너무 자신있게 의존하고 너무 약하게 의존하는 것보다 지나치게 의존하고 남은 생애 동안 항상 미신적 두려움을 코딩하는 모호한 최첨단 사례를 찾아 내고 싶습니다. 적어도 상황에 따라 빨리 실행되지 않고 미신이 아닌 귀중한 지식을 얻는다면 프로파일 러를 찾아서 제대로 조사해야합니다.

옵티 마이저에 의지하기 위해 잘하고 있습니다. 유지하십시오. 옵티마이 저의 단점에 대한 오도 된 두려움으로부터 프로파일 링하기 전에 루프에서 호출 된 모든 함수를 명시 적으로 요청하기 시작하는 사람과 같이되지 마십시오.

프로파일 링

프로파일 링은 실제로 로터리이지만 귀하의 질문에 대한 궁극적 인 답변입니다. 효율적인 코드를 작성하고자하는 초보자는 종종 최적화 해야하는 것이 아니라 최적화 되지 않는 것입니다. 인간적으로 직관적이지만 계산 상 잘못된 비 효율성에 대한 모든 종류의 잘못된 유도를 개발하기 때문입니다. 프로파일 러에 대한 경험을 개발하면 자신있게 기대할 수있는 컴파일러의 최적화 기능뿐만 아니라 하드웨어의 기능 (제한도 포함)에 대한 적절한 평가를받을 수 있습니다. 무엇을 배우는 것보다 최적화 할 가치가없는 것을 배우는 데있어서 프로파일 링에는 더 많은 가치가있을 것입니다.


-1

소프트웨어는 매우 다양한 플랫폼에서 다양한 목적으로 C ++로 작성 될 수 있습니다.

그것은 소프트웨어의 목적에 전적으로 달려 있습니다. 리팩터링 등 유지 보수, 확장, 패치, 관리가 쉬운 경우 성능, 비용 또는 특정 하드웨어와의 호환성 또는 개발하는 데 걸리는 시간과 같은 다른 것들이 더 중요합니다.


-2

나는 이것에 대한 지루한 대답은 '그것은 달려있다'고 생각합니다.

그것은 컴파일러 최적화에 의존하는 코드를 작성하는 나쁜 관행 해제 될 가능성을 하고 취약점이 문서화되지는 어디 문제의 코드는 단위 테스트되지 않은 상태가 중단 한 경우에 당신은 그것을 알고 줄 수 있도록 ? 아마.

그것은 컴파일러 최적화에 의존하는 코드를 작성하는 나쁜 관행 해제 될 가능성이되지는 즉, 문서화단위 테스트 ? 아마.


-6

당신이 우리에게 말하지 않는 것이 더 이상 없다면, 이것은 나쁜 습관이지만, 당신이 제안한 이유가 아닙니다.

이전에 사용한 다른 언어와 달리 C ++에서 객체의 값을 반환하면 객체의 사본이 생성됩니다. 그런 다음 객체를 수정하면 다른 객체 가 수정 됩니다. 그게 내가있는 경우이며, Obj a; a.x=1;그리고 Obj b = a;그 다음 내가 할, b.x += 2; b.f();다음, a.x여전히 3, 1을하지 같습니다.

따라서 객체를 참조 또는 포인터 대신 값으로 사용하면 동일한 기능이 제공되지 않으므로 소프트웨어에 버그가 생길 수 있습니다.

아마도 당신은 이것을 알고 당신의 특정 사용 사례에 부정적인 영향을 미치지 않습니다. 그러나 귀하의 질문에 나오는 문구에 따르면 귀하는 그 차이를 알지 못하는 것 같습니다. "함수에서 객체 생성"과 같은 표현

"함수에서 객체 생성" new Obj;은 "객체를 값으로 반환"처럼Obj a; return a;

Obj a;Obj* a = new Obj;매우 다른 것입니다; 전자는 올바르게 사용하고 이해하지 않으면 메모리 손상을 초래할 수 있으며 후자는 올바르게 사용하고 이해하지 않으면 메모리 누수를 초래할 수 있습니다.


8
RVO (Return Value Optimization)는 컴파일러가 스택 프레임에서 반환 된 객체를 한 레벨 위로 구성하여 불필요한 객체 복사를 피하는 잘 정의 된 의미론입니다. 이것은 C ++ 17에서 위임되기 오래 전에 지원 된 잘 정의 된 동작입니다. 10-15 년 전까지 만해도 모든 주요 컴파일러는이 기능을 지원했으며 일관되게 수행했습니다.

@Snowman 저는 물리적 인 저수준 메모리 관리에 대해 이야기하고 있지 않으며 메모리 팽창이나 속도에 대해서는 이야기하지 않았습니다. 내가 구체적으로 대답했듯이 논리 데이터에 대해 이야기하고 있습니다. 논리적으로 , 객체의 값을 제공하면 컴파일러가 구현되는 방식이나 장면 뒤에서 사용되는 어셈블리에 관계없이 객체의 사본이 생성됩니다. 비하인드 스토리 레벨의 저것은 하나이고 언어의 논리적 구조와 행동은 또 다른 것입니다. 그것들은 서로 관련되어 있지만 같은 것은 아닙니다. 둘 다 이해해야합니다.
Aaron

6
당신의 대답은 "C ++에서 객체의 값을 반환하면 객체의 사본을 생성합니다"라고 말합니다. RVO의 맥락에서 완전히 거짓입니다. 객체는 호출 위치에 직접 구성 되며 복사는 이루어지지 않습니다. 복사 생성자를 삭제하고 RVO에 필요한 명령문에 구성된return 오브젝트 리턴하여이를 테스트 할 수 있습니다 . 또한 newRVO가 아닌 키워드 와 포인터 에 대해 이야기 합니다. 나는 당신이 그 질문을 이해하지 못하거나 RVO 또는 둘 다를 이해한다고 생각합니다.

-7

Pieter B는 가장 놀랍도록 추천하는 데 절대적으로 정확합니다.

특정 질문에 대답하기 위해 C ++에서 이것이 (의 가능성이 있음) 의미 std::unique_ptr는 생성 된 객체 로 a 를 반환해야한다는 것 입니다.

그 이유는 C ++ 개발자 에게 무슨 일이 일어나고 있는지 분명 하기 때문입니다 .

귀하의 접근 방식이 가장 효과적이지만 실제로는 그렇지 않은 경우 객체가 작은 값 유형임을 효과적으로 신호합니다. 또한 인터페이스 추상화 가능성을 버리고 있습니다. 이것은 현재의 목적으로는 괜찮을 수도 있지만 행렬을 다룰 때 종종 매우 유용합니다.

다른 언어에서 온 경우 모든시길이 처음에 혼란 스러울 수 있습니다. 그러나 코드를 사용하지 않으면 코드가 더 명확 해 진다고 가정하지 않도록주의하십시오. 실제로는 그 반대 일 수 있습니다.


로마에있을 때 로마인들이하는 것처럼하십시오.

14
동적 할당을 자체 수행하지 않는 유형에는 적합하지 않습니다. OP가 자신의 유스 케이스에서 자연스럽게 가치를 반환한다고 느끼는 것은 객체가 호출자 측에서 자동 저장 기간을 갖는다는 것을 나타냅니다. 단순하고 너무 크지 않은 객체의 경우 순진한 복사 반환 값 구현조차도 동적 할당보다 몇 배 더 빠릅니다. (반면, 함수가 컨테이너를 반환하면 고유 컴파일러를 반환하는 것이 순진한 컴파일러 반환 값에 비해 유리할 수도 있습니다.)
Peter A. Schneider

9
@Matt 당신이 이것을 모르는 경우에 이것은 최선의 방법이 아닙니다. 불필요하게 메모리 할당을 수행하고 사용자에게 포인터 의미를 강요하는 것은 좋지 않습니다.
nwp

5
우선, 스마트 포인터를 사용할 때는 직접이 std::make_unique아닌을 반환해야합니다 std::unique_ptr. 두 번째로, RVO는 난해한 공급 업체별 최적화가 아닙니다. 표준에 적용됩니다. 그렇지 않았을 때에도 널리 지원되어 예상되는 동작이었습니다. std::unique_ptr처음에는 포인터가 필요하지 않을 때 포인트를 반환 하지 않습니다.

4
@ Snowman : "없었을 때"는 없습니다. 최근에야 필수 사항 이었지만 모든 C ++ 표준은 항상 [N] RVO를 인식했으며이를 가능하게하기 위해 조정했습니다 (예 : 컴파일러는 항상 반환 값에 대해 복사 생성자 사용을 생략 할 수있는 명시적인 권한을 부여 받았습니다). 눈에 보이는 부작용이 있습니다).
Jerry Coffin
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.