Stroustrup의 "The C ++ Programming Language"를 읽고 있었는데, 여기서 그는 변수에 무언가를 추가하는 두 가지 방법 중
x = x + a;
과
x += a;
그는 더 +=
잘 구현 될 가능성이 높기 때문에 선호합니다 . 나는 그가 더 빨리 작동한다는 것을 의미한다고 생각합니다.
하지만 정말로 요? 컴파일러 및 기타 사항에 따라 다르면 어떻게 확인합니까?
Stroustrup의 "The C ++ Programming Language"를 읽고 있었는데, 여기서 그는 변수에 무언가를 추가하는 두 가지 방법 중
x = x + a;
과
x += a;
그는 더 +=
잘 구현 될 가능성이 높기 때문에 선호합니다 . 나는 그가 더 빨리 작동한다는 것을 의미한다고 생각합니다.
하지만 정말로 요? 컴파일러 및 기타 사항에 따라 다르면 어떻게 확인합니까?
답변:
모든 컴파일러 가치는 소금 정확히 어떤 내장 유형 (모두 구조에 대한 동일한 시스템 언어 시퀀스를 생성 int
, float
긴 문장이 정말 단순하게만큼, 등) x = x + a;
및 최적화하는 것이 활성화된다 . (특히 -O0
기본 모드 인 GCC 는 디버거가 항상 변수 값을 찾을 수 있도록 완전히 불필요한 저장소를 메모리에 삽입하는 것과 같은 최적화 방지를 수행 합니다.)
그러나 진술이 더 복잡하다면 다를 수 있습니다. f
포인터를 반환하는 함수 라고 가정 하면
*f() += a;
f
한 번만 호출 하는 반면
*f() = *f() + a;
두 번 호출합니다. 경우 f
부작용이있다, 둘 중 하나는 (후자 아마) 잘못된 것입니다. 설사f
부작용이 컴파일러는 두 번째 호출을 제거하지 못할 수 있으므로 후자는 실제로 더 느릴 수 있습니다.
그리고 우리가 여기서 C ++에 대해 이야기하고 있기 때문에, operator+
과 operator+=
. 경우 x
최적화하기 전에 - - 다음 같은 유형이다 x += a
로 변환
x.operator+=(a);
반면에 x = x + a
번역
auto TEMP(x.operator+(a));
x.operator=(TEMP);
이제 클래스가 제대로 작성 되고 컴파일러의 옵티마이 저가 충분하면 둘 다 동일한 기계어를 생성하지만 내장 유형과 같은 확실한 것은 아닙니다. 이것은 아마도 Stroustrup이 +=
.
expr
에 var
있다 var+=expr
하고 그것을 독자를 혼동 할 다른 방법을 쓰기.
*f() = *f() + a;
당신은 ... 당신이 정말로 무엇을 달성하고 싶은지에 좋은 열심히 살펴 봐야 할 수 있습니다
1
상수, a
휘발성, 사용자 정의 유형 등이 될 수 있습니다. 완전히 다릅니다. 사실 나는 이것이 어떻게 종결되었는지 이해하지 못한다.
동일하게 될 dissasembly를보고 확인할 수 있습니다.
기본 유형의 경우 둘 다 똑같이 빠릅니다.
이것은 디버그 빌드에 의해 생성 된 출력입니다 (예 : 최적화 없음).
a += x;
010813BC mov eax,dword ptr [a]
010813BF add eax,dword ptr [x]
010813C2 mov dword ptr [a],eax
a = a + x;
010813C5 mov eax,dword ptr [a]
010813C8 add eax,dword ptr [x]
010813CB mov dword ptr [a],eax
사용자 정의 유형 이 오버로드 할 수 있습니다, operator +
그리고 operator +=
, 그것은 각각의 구현에 따라 달라집니다.
a
한하고 x
있다 volatile
컴파일러가 생성 할 수 있습니다 inc DWORD PTR [x]
. 이것은 느립니다.
operator +
아무것도하지 않고 operator +=
100000 번째 소수를 계산 한 다음 반환하도록 구현할 수 있습니다. 물론 그것은 어리석은 일이지만 가능합니다.
++x
와 temp = x + 1; x = temp;
대부분의 아마 ++ 조립 오히려 C 이상에서 작성해야 다음, ...
+=
컴파일러의 삶을 훨씬 더 쉽게 만들고 있다고 말한다면 . 컴파일러 x = x+a
가 이것이와 동일한 것을 인식 x += a
하려면 컴파일러는
왼쪽 ( x
)을 분석하여 부작용이없고 항상 동일한 l- 값을 참조하는지 확인합니다. 예를 들어, 일 수 있으며 z[i]
둘 다 z
및 i
변경되지 않도록해야합니다 .
오른쪽 ( x+a
)을 분석하고 합계인지 확인하고에서와 같이 변환 할 수 있더라도 왼쪽이 오른쪽에서 한 번만 발생하는지 확인합니다 z[i] = a + *(z+2*0+i)
.
당신이 의미하는 것이에 추가 a
하는 것이라면 x
컴파일러 작성자는 당신이 의미하는 바를 말할 때 그것을 고맙게 생각합니다. 그렇게하면 작성자 가 모든 버그를 제거 하기를 바라는 컴파일러의 일부 를 사용하지 않고 솔직히 머리를 뺄 수없는 경우 가 아니라면 실제로 삶을 더 쉽게 만들 수 없습니다. 포트란 모드의.
구체적인 예를 들어 간단한 복소수 유형을 상상해보십시오.
struct complex {
double x, y;
complex(double _x, double _y) : x(_x), y(_y) { }
complex& operator +=(const complex& b) {
x += b.x;
y += b.y;
return *this;
}
complex operator +(const complex& b) {
complex result(x+b.x, y+b.y);
return result;
}
/* trivial assignment operator */
}
a = a + b의 경우 추가 임시 변수를 만든 다음 복사해야합니다.
당신은 잘못된 질문을하고 있습니다.
이로 인해 앱이나 기능의 성능이 향상되지는 않습니다. 그랬더라도 알아내는 방법 은 코드 를 프로파일 링 하고 그것이 당신에게 어떤 영향을 미치는지 확실히 아는 것입니다. 이 수준에서 어느 것이 더 빠른지 걱정하는 대신 명확성, 정확성 및 가독성 측면에서 생각하는 것이 훨씬 더 중요 합니다.
이것은 중요한 성능 요소이더라도 컴파일러는 시간이 지남에 따라 진화한다는 것을 고려할 때 특히 그렇습니다. 누군가가 새로운 최적화를 알아 내고 오늘의 정답이 내일 잘못 될 수 있습니다. 조기 최적화의 전형적인 경우입니다.
이것은 성능이 전혀 중요하지 않다는 말이 아닙니다. 단지 성능 목표를 달성하기위한 잘못된 접근 방식이라는 것입니다. 올바른 접근 방식은 프로파일 링 도구를 사용하여 코드가 실제로 시간을 소비하는 위치와 노력을 집중할 위치를 파악하는 것입니다.
기계와 아키텍처에 따라 달라져야한다고 생각합니다. 아키텍처가 간접 메모리 주소 지정을 허용하는 경우 컴파일러 작성자는 최적화를 위해이 코드를 대신 사용할 수 있습니다.
mov $[y],$ACC
iadd $ACC, $[i] ; i += y. WHICH MIGHT ALSO STORE IT INTO "i"
반면 i = i + y
에 (최적화없이) 번역 될 수 있습니다.
mov $[i],$ACC
mov $[y],$B
iadd $ACC,$B
mov $B,[i]
i
가 포인터를 반환하는 함수 등의 다른 문제 도 고려해야합니다. GCC를 포함한 대부분의 프로덕션 수준 컴파일러는 두 문에 대해 동일한 코드를 생성합니다 (정수인 경우).