source

const-correct는 컴파일러에 최적화를 위한 더 많은 공간을 제공합니까?

manycodes 2022. 10. 27. 23:03
반응형

const-correct는 컴파일러에 최적화를 위한 더 많은 공간을 제공합니까?

가독성이 향상되고 프로그램의 에러 발생률이 낮아진다는 것은 알고 있습니다만, 퍼포먼스는 어느 정도 향상됩니까?

그리고 참고로, 레퍼런스와 레퍼런스의 주요 차이점은 무엇입니까?const포인터?기억 속에 저장되는 방식이 다를 것 같은데, 어떻게요?

즉석에서 두 가지 경우를 생각할 수 있는데const-qualification은 추가 최적화를 허용합니다(전체 프로그램 분석을 사용할 수 없는 경우).

const int foo = 42;
bar(&foo);
printf("%i", foo);

여기서 컴파일러는 인쇄하는 방법을 알고 있습니다.42몸을 검사할 필요 없이bar()(커런트 변환 유닛에서는 표시되지 않을 수 있습니다)에 대한 모든 수정이foo불법입니다(Nemo의 예 동일).

단, 이것은 표시 없이도 가능합니다.foo~하듯이const선언함으로써bar()~하듯이

extern void bar(const int *restrict p);

많은 경우에 프로그래머는 실제로 원하는 것은restrict- 한정 포인터 -const그리고 쉬운 조언이 아니라...const함수 파라미터로 사용됩니다.이는 포인팅 대상 객체의 가변성을 보증하는 것은 전자뿐이기 때문입니다.

질문의 두 번째 부분에 대해:모든 실용적인 목적을 위해 C++ 참조는 자동 인터다이렉션을 사용하는 상수 포인터(일정한 값에 대한 포인터가 아님)로 생각할 수 있습니다. 포인터보다 '안전'하거나 '빠른' 것이 아니라 단지 더 편리할 뿐입니다.

[편집 : OK 그래서 이 질문은 처음에 생각했던 것보다 미묘합니다]

상수 포인터 또는 상수 참조를 선언하는 것은 컴파일러의 최적화에 전혀 도움이 되지 않습니다(단, 이 답변 하단의 업데이트 참조).

constdeclaration은 선언 범위 에서 식별자를 사용하는 방법을 나타낼 뿐 기본 개체를 변경할 수 없음을 나타내는 것은 아닙니다.

예:

int foo(const int *p) {
    int x = *p;
    bar(x);
    x = *p;
    return x;
}

컴파일러는 다음과 같이 가정할 수 없습니다.*p에 대한 호출에 의해 변경되지 않습니다.bar(),왜냐면p(예를 들어) 글로벌 int에 대한 포인터가 될 수 있으며bar()수정할 수 있습니다.

컴파일러가 발신자에 대해 충분히 알고 있는 경우foo()및 의 내용bar()증명할 수 있다bar()수정하지 않다*pconst 선언 없이 증명도 실행할 수 있습니다.

하지만 이것은 일반적으로 사실이다.왜냐면const선언 범위 내에서만 효과가 있습니다.컴파일러는 이미 그 범위 내에서 포인터 또는 참조를 어떻게 처리하고 있는지 확인할 수 있습니다.기본 오브젝트를 수정하고 있지 않은 것을 이미 인식하고 있습니다.

요컨대, 모두const이 문맥에서 하는 것은 실수를 방지하는 것입니다.컴파일러에 이미 알고 있지 않은 것을 알려주지 않기 때문에 최적화와는 무관합니다.

호출하는 함수는?foo()? 예를 들면:

int x = 37;
foo(&x);
printf("%d\n", x);

컴파일러는 이것이 37을 인쇄한다는 것을 증명할 수 있습니까?foo()를 취득하다const int *?

아니, 그래도foo()는 상수 포인터를 사용하여 const-ness를 버리고 int를 변경할 수 있습니다(이는 정의되지 않은 동작이 아닙니다).여기서도 컴파일러는 일반적으로 어떠한 가정도 할 수 없습니다.또한 컴파일러가 다음 정보를 충분히 알고 있다면foo()그러한 최적화를 하기 위해, 그것은 심지어 그것이 없어도const.

유일한 시간const최적화가 가능한 경우는 다음과 같습니다.

const int x = 37;
foo(&x);
printf("%d\n", x);

여기서 수정하려면x어떤 메커니즘을 통해서든 (예를 들어 포인터를 가져다가 버림으로써)const)는 정의되지 않은 동작을 호출합니다.따라서 컴파일러는 사용자가 그렇게 하지 않는다고 가정할 수 있으며 상수 37을 printf()에 전파할 수 있습니다.이러한 최적화는 선언하는 모든 개체에 대해 합법적입니다.const(실제로 참조를 하지 않는 로컬 변수는 컴파일러가 범위 내에서 변경할 수 있는지 여부를 확인할 수 있기 때문에 도움이 되지 않습니다.)

당신의 "side note" 질문에 답하기 위해, (a) const 포인터는 포인터이고, (b) const 포인터는 NULL과 같을 수 있습니다. 내부 표현(주소)은 거의 같습니다.

[갱신]

Christoph가 댓글에서 지적했듯이, 제 답변은 불완전합니다. 왜냐하면 그것은 언급되지 않기 때문입니다.restrict.

C99 표준의 섹션 6.7.3.1(4)에는 다음과 같이 기술되어 있다.

B의 각 실행 중에 L은 P에 근거하여 &L을 갖는 임의의 l값으로 합니다.L을 사용하여 지정한 오브젝트 X의 값에 액세스하고 X도 (수단에 따라) 변경되면 다음 요건이 적용됩니다.T는 항상 자격이 주어지지 않는다.

(여기서 B는 T에 대한 제한 포인트인 P가 범위 내에 있는 기본 블록입니다).

그래서 만약 C 함수가foo()는 다음과 같이 선언됩니다.

foo(const int * restrict p)

...그러면 컴파일러는 에 대한 수정이 없다고 가정할 수 있습니다.*p생전에 일어나다p-- 즉, 실행 중foo()--그렇지 않으면 동작이 정의되지 않기 때문입니다.

그래서 원칙적으로, 결합은restrict상수로의 포인터를 사용하면, 상기의 양쪽 모두의 최적화를 유효하게 할 수 있습니다.실제로 이러한 최적화를 실장하고 있는 컴파일러가 있습니까(GCC 4.5.2는 적어도 없습니다).

주의:restrictC에만 존재하며 C++(C++0x도 아님)는 컴파일러 고유의 확장자로만 존재합니다.

에는 두 가지 문제가 있습니다.constC++(최적화에 관한 한):

  • const_cast
  • mutable

const_castconst reference 또는 const pointer를 사용하여 객체를 전달하더라도 함수는 const-ness를 무시하고 객체를 수정할 수 있습니다(개체가 처음부터 constant가 아닌 경우 허용됨).

mutable어떤 물체가 비록 그렇더라도const일부 부품은 수정될 수 있습니다(예: 동작).또, (소유하는 것이 아니라) 가리키는 오브젝트는, 다음과 같이 변경할 수 있습니다.const메서드가 논리적으로 오브젝트 상태의 일부인 경우에도 마찬가지입니다.마지막으로 글로벌 변수도 수정할 수 있습니다.

const개발자가 논리적 오류를 조기에 발견할 수 있도록 지원합니다.

const-correctness는 일반적으로 퍼포먼스에 도움이 되지 않습니다.대부분의 컴파일러는 프런트엔드를 넘어서는 constance를 추적하는 것조차 귀찮습니다.상황에 따라 변수를 const로 표시하면 도움이 됩니다.

참조와 포인터는 메모리에 정확히 동일한 방식으로 저장됩니다.

이것은 실제로 컴파일러/플랫폼에 따라 달라집니다(아직 작성되지 않은 컴파일러나 사용하지 않는 플랫폼에서 최적화에 도움이 될 수 있습니다).C 및 C++ 규격에서는 일부 기능에 대한 복잡성 요건을 제공하는 것 외에 성능에 대해서는 언급하지 않습니다.

컨스턴트에 대한 포인터와 참조는 일반적으로 최적화에 도움이 되지 않는다. 왜냐하면 일부 상황에서는 컨스턴스 자격이 법적으로 폐기될 수 있고 다른 비컨스턴트 참조에 의해 오브젝트가 수정될 수 있기 때문이다.한편, 오브젝트를 항상이라고 선언하는 것은 오브젝트를 변경할 수 없다는 것을 보증하기 때문에 도움이 됩니다(컴파일러가 정의를 모르는 함수에 전달된 경우에도).이를 통해 컴파일러는 const 객체를 읽기 전용 메모리에 저장하거나 그 값을 중앙 집중식 장소에 캐시할 수 있으므로 복사 및 수정 체크가 필요하지 않습니다.

포인터와 참조는 보통 동일한 방법으로 구현되지만, 이는 완전히 플랫폼에 의존합니다.정말로 흥미가 있는 경우는, 사용하고 있는 플랫폼과 컴파일러의 머신 코드를 프로그램에서 확인할 필요가 있습니다(머신 코드를 생성하는 컴파일러를 사용하고 있는 경우).

우선 글로벌 변수 const를 선언하면 라이브러리 또는 실행 파일의 읽기 전용 부분에 삽입하여 읽기 전용 mmap을 사용하여 여러 프로세스 간에 공유할 수 있습니다.이는 적어도 글로벌 변수에서 선언된 많은 데이터가 있는 경우 Linux에서 큰 메모리 이점이 될 수 있습니다.

또 다른 상황에서는 상수 글로벌 정수를 선언하거나 플로트 또는 열거를 선언하면 컴파일러는 변수 참조를 사용하지 않고 상수 인라인만 입력할 수 있습니다.컴파일러 전문가는 아니지만 그게 좀 빠른 것 같아요.

레퍼런스는 구현에 관한 기본적인 포인터일 뿐입니다.

이 기능은 성능에 다소 도움이 되지만 선언을 통해 개체에 직접 액세스하는 경우에만 유용합니다.참조 파라미터 등은 최적화할 수 없습니다.이는 원래 const로 선언되지 않은 객체에 대한 다른 경로가 있을 수 있기 때문입니다.또한 참조하고 있는 객체가 실제로 const로 선언되었는지 아닌지는 일반적으로 컴파일러가 판단할 수 없습니다.

const 선언을 사용하는 경우 컴파일러는 외부에서 컴파일된 함수 본문 등이 수정할 수 없다는 것을 알 수 있으므로 이점을 얻을 수 있습니다.물론 const int와 같은 것은 컴파일 시에 전파되기 때문에 (단순한 int에 비해) 큰 이점이 있습니다.

참조와 포인터는 정확히 동일하게 저장되며 구문적으로만 다르게 동작합니다.참조는 기본적으로 리네밍이므로 비교적 안전한 반면 포인터는 여러 가지 다른 점을 가리킬 수 있기 때문에 더 강력하고 오류가 발생하기 쉽습니다.

const pointer는 참조와 구조적으로 동일하기 때문에 기계 코드와 효율은 동일할 것입니다.진짜 차이점은 구문입니다. 참조는 보다 깔끔하고 읽기 쉬운 구문이며, 포인터에 의해 제공되는 추가 기계는 필요하지 않기 때문에 참조가 스타일상 선호됩니다.

ReferenceURL : https://stackoverflow.com/questions/6313730/does-const-correctness-give-the-compiler-more-room-for-optimization

반응형