C ++ 컴파일러는 쓸모없는 괄호를 제거 / 최적화합니까?


19

코드가

int a = ((1 + 2) + 3); // Easy to read

보다 느리게 달리다

int a = 1 + 2 + 3; // (Barely) Not quite so easy to read

또는 "불필요한"괄호를 제거 / 최적화 할만큼 현명한 컴파일러가 영리한가?

매우 작은 최적화 문제처럼 보이지만 C # / Java / ... 대신 C ++를 선택하는 것은 최적화 (IMHO)에 관한 것입니다.


9
C #과 Java 도이를 최적화 할 것이라고 생각합니다. 나는 그들이 AST를 파싱하고 만들 때, 그처럼 쓸모없는 것을 제거 할 것이라고 믿습니다.
Farid Nouri Neshat

5
내가 읽은 모든 것은 JIT 컴파일을 통해 사전 컴파일로 쉽게 돈을 벌 수 있기 때문에 매우 독창적 인 주장이 아닙니다. JIT 컴파일을 사용하면 컴파일러가 언제 시작하고 코드 컴파일을 시작할지 알 수 없기 때문에 게임 프로그래밍을 시작합니다. 사전 컴파일을 선호하는 실제 이유는 예측 가능하기 때문입니다. 그러나 네이티브 코드에 대한 사전 컴파일은 가비지 수집과 상호 배타적이지 않습니다. 예를 들어 표준 ML 및 D를 참조하십시오. 가비지 수집이 더 효율적이라는 설득력있는 주장을 보았습니다 ...
Doval

7
... RAII 및 스마트 포인터보다, 잘 알려진 경로 (C ++)와 그 언어로 게임 프로그래밍을 수행하는 비교적 읽지 않은 경로를 따르는 것이 더 중요합니다. 또한 괄호에 대한 걱정은 미쳤다는 점에 주목할 것입니다. 어디에서 왔는지 알지만 우스운 미세 최적화입니다. 프로그램에서 데이터 구조와 알고리즘을 선택하면 그러한 사소한 것이 아니라 성능을 확실히 지배 할 수 있습니다.
Doval

6
음 ... 정확히 어떤 종류의 최적화 를 기대하십니까? 정적 분석에 대해 이야기하고 있다면, 내가 아는 대부분의 언어에서 이것은 정적으로 알려진 결과로 대체 될 것입니다 (LLVM 기반 구현은 AFAIK를 시행합니다). 실행 순서에 대해 이야기하는 경우 동일한 작업이며 부작용이 없으므로 중요하지 않습니다. 어쨌든 두 개의 피연산자가 필요합니다. 그리고 이것을 사용하여 성능과 관련하여 C ++, Java 및 C #을 비교하는 경우 최적화가 무엇이며 어떻게 작동하는지에 대한 명확한 아이디어가없는 것처럼 들리므로 대신 학습에 집중해야합니다.
Theodoros Chatzigiannakis

5
왜 궁금 A) 는 괄호에 표현보다 읽기 고려 (그들은이 특별한 순서를 강조 왜 그냥 오해의 소지가 추한 외모 나에 (?한다, 그것은 여기에 assiciative하지)와 투박한) b)는 당신이없이 생각할 것 이유 괄호로 인해 성능이 더 좋아질 수 있습니다 (정확하게 구문 분석 하는 것은 기계에 대해 운영자 수정 사항을 추론하는 것보다 쉽습니다 . Marc van Leuwen이 말했듯이 이것은 런타임에 전혀 영향을 미치지 않습니다).
leftaroundabout

답변:


87

컴파일러는 실제로 괄호를 삽입하거나 제거하지 않습니다. 표현식에 해당하는 구문 분석 트리 (괄호가없는)를 작성하기 때문에 작성한 괄호를 존중해야합니다. 표현을 완전히 괄호로 묶으면 구문 분석 트리가 무엇인지 인간에게 즉시 알 수 있습니다. 과도하게 중복 된 괄호를 극단으로 가면 int a = (((0)));결과 구문 분석 트리 (및 생성 된 코드를 변경하지 않고 파서의 일부 사이클을 낭비하면서 판독기의 뉴런에 쓸모없는 스트레스를 유발합니다 ) 가장 작은 비트.

괄호 를 쓰지 않으면 구문 분석기는 구문 분석 트리를 작성하는 작업을 계속 수행해야하며 연산자 우선 순위 및 연관성 규칙에 따라 구문 분석 트리를 작성해야합니다. 이러한 규칙은 컴파일러에 코드에 삽입 해야 (암시 적) 괄호를 알려주는 것으로 간주 할 수 있지만 파서는이 경우 괄호를 실제로 처리하지는 않지만 괄호처럼 동일한 구문 분석 트리를 생성하도록 구성되었습니다. 특정 장소에있었습니다. int a = (1+2)+3;(의 연관성은 +왼쪽에 있음) 에서 와 같이 정확하게 그 장소에 괄호를 넣으면 파서는 약간 다른 경로로 동일한 결과에 도달합니다. 에서와 같이 다른 괄호를 넣으면int a = 1+(2+3);그런 다음 다른 구문 분석 트리를 강제 실행하여 다른 코드가 생성 될 수 있습니다 ( 결과 코드 를 실행 하는 효과 가 결코 다르지 않는 한 컴파일러가 구문 분석 트리를 작성한 후 변환을 적용 할 수 있기 때문에 그렇지 않을 수도 있음) 그것). 생성 코드에 차이가 있다고 가정하면 일반적으로 어느 것이 더 효율적인지에 대해서는 말할 수 없습니다. 가장 중요한 요점은 물론 대부분의 경우 구문 분석 트리가 수학적으로 동등한 표현을 제공하지 않으므로 실행 속도를 비교하는 것이 요점을 벗어납니다. 표현을 작성하면 적절한 결과를 얻을 수 있습니다.

결과는 다음과 같습니다. 정확성과 가독성을 위해 필요에 따라 괄호를 사용하십시오. 중복되는 경우 실행 속도에 전혀 영향을 미치지 않으며 컴파일 시간에는 무시할만한 영향을 미칩니다.

그리고이 중 어느 것도 최적화와 관련 이 없으며 , 이는 구문 분석 트리가 작성된 후에 발생하므로 구문 분석 트리가 어떻게 구성되었는지 알 수 없습니다. 이것은 가장 오래되고 어리석은 컴파일러에서 가장 똑똑하고 가장 현대적인 컴파일러로 변경하지 않고 적용됩니다. 해석 언어 ( "컴파일 시간"과 "실행 시간"이 일치하는 경우)에서만 중복 괄호에 대한 패널티가 부과 될 수 있지만 대부분의 이러한 언어는 적어도 구문 분석 단계가 한 번만 수행되도록 구성되어 있다고 생각합니다. 각 문장마다 (구문 분석 된 형태로 실행을 위해 저장).


s / oldes / oldest /. 좋은 대답, +1
David Conrad

23
총 nitpick 경고 : "질문이 잘 작성되지 않았습니다."— "잘 들어 맞음"을 사용하여 "querent가 원하는 지식 격차를 분명히 암시"한다는 의미로 동의하지 않습니다. 본질적으로 문제는 "X 최적화, A 또는 B 선택, 그리고 왜? 아래에서 어떤 일이 발생합니까?"라는 것입니다. 적어도 지식 격차가 무엇인지 매우 명확하게 제안합니다. 당신이 올바르게 지적하고 다루는 질문의 결함은 그것이 잘못된 정신 모델에 기반한다는 것입니다.
Jonas Kölker

를 들어 a = b + c * d;, a = b + (c * d);것 [무해한] 중복 괄호. 그들이 코드를 더 읽기 쉽게 만드는 데 도움이된다면 좋습니다. a = (b + c) * d;중복되지 않는 괄호입니다. 실제로 결과 구문 분석 트리를 변경하고 다른 결과를 제공합니다. 완벽하게 합법적이지만 (실제로 필요하지만) 암시 적 기본 그룹화와 동일하지 않습니다.
Phil Perry

1
@OrangeDog 사실, 부끄러운 벤의 의견에 따르면 VM을 원어민보다 빠르다고 주장하는 사람들이 많았습니다.
gbjbaanb

1
@ JonasKölker : 내 첫 문장은 실제로 제목에 공식화 된 질문을 나타냅니다. 컴파일러가 괄호를 삽입하거나 제거하는지에 대한 질문에 실제로 대답 할 수는 없습니다. 그러나 어떤 지식 격차를 해결해야하는지 분명하게 동의합니다.
Marc van Leeuwen

46

괄호는 컴파일러가 아닌 귀하의 이익을 위해서만 있습니다. 컴파일러는 명령문을 나타내는 올바른 기계 코드를 작성합니다.

참고로, 컴파일러는 가능한 경우 완전히 최적화 할 수있을 정도로 영리합니다. 귀하의 예에서, 이것은 int a = 6;컴파일 타임에 설정됩니다.


9
절대적으로-원하는만큼 괄호를 붙여 컴파일러가 원하는 것을 알아내는 데 많은 노력을 기울 이도록하십시오.
gbjbaanb

23
@Serge 진정한 프로그래밍은 성능보다는 코드의 가독성에 관한 것입니다. 내년에 충돌을 디버깅해야하고 "최적화 된"코드 만 가지고있을 때 당신은 미워할 것입니다.
ratchet freak

1
@ratchetfreak, 당신은 그렇습니다. 나는 또한 내 코드를 주석 처리하는 방법을 알고 있습니다. int a = 6; // = (1 + 2) + 3
Serge

20
@Serge 나는 1 년의 조정 후에 시간이 걸리지 않는다는 것을 알고 있습니다. 시간 주석과 코드가 동기화되지 않을 것입니다.int a = 8;// = 2*3 + 5
ratchet freak

21
또는 www.thedailywtf.com에서 :int five = 7; //HR made us change this to six...
Mooing Duck

23

실제로 질문 한 질문에 대한 대답은 '아니요'이지만 질문하려는 질문에 대한 대답은 '예'입니다. 괄호를 추가해도 코드 속도가 느려지지 않습니다.

최적화에 대해 질문했지만 괄호는 최적화와 관련이 없습니다. 컴파일러는 생성 된 코드의 크기 나 속도를 개선하기 위해 다양한 최적화 기술을 적용합니다 (때로는 둘 다). 예를 들어, A ^ 2 (A 제곱) 표현식을 사용하고 더 빠른 경우 A x A (A를 곱한 값)로 대체 할 수 있습니다. 여기서 대답은 '아니요'입니다. 컴파일러는 괄호가 있는지 여부에 따라 최적화 단계에서 별다른 차이가 없습니다.

가독성을 향상시킬 수 있다고 생각되는 곳에서 표현식에 불필요한 괄호를 추가하면 컴파일러가 여전히 동일한 코드를 생성하는지 묻고 싶다고 생각합니다. 즉, 괄호를 추가하면 컴파일러가 어떻게 든 더 나쁜 코드를 생성하지 않고 다시 추출 할 수있을 정도로 똑똑합니다. 항상 그렇습니다.

내가 조심스럽게 말하겠습니다. 엄밀히 불필요한 표현식에 괄호를 추가하면 (표현식의 의미 또는 평가 순서에 영향을주지 않음) 컴파일러는 자동으로이를 무시하고 동일한 코드를 생성합니다.

그러나 분명히 불필요한 괄호가 실제로 표현식의 평가 순서를 변경하는 특정 표현식이 있으며,이 경우 컴파일러는 실제로 작성한 내용을 적용하는 코드를 생성하여 의도 한 것과 다를 수 있습니다. 다음은 예입니다. 이러지 마!

short int a = 30001, b = 30002, c = 30003;
int d = -a + b + c;    // ok
int d = (-a + b) + c;  // ok, same code
int d = (-a + b + c);  // ok, same code
int d = ((((-a + b)) + c));  // ok, same code
int d = -a + (b + c);  // undefined behaviour, different code

원하는 경우 괄호를 추가하되 실제로 불필요하게하십시오!

난 절대 안해 실제 이익이없는 오류의 위험이 있습니다.


각주 : 부호없는 동작은 부호있는 정수 표현식이 표현할 수있는 범위를 벗어난 값 (이 경우 -32767 ~ +32767)으로 평가 될 때 발생합니다. 이것은이 답변의 범위를 벗어난 복잡한 주제입니다.


마지막 줄에 정의되지 않은 동작은 부호있는 short가 부호 뒤에 15 비트 만 있기 때문에 최대 크기는 32767입니다. 이 간단한 예제에서 컴파일러는 오버플로에 대해 경고해야합니다. 어느 쪽이든 반대의 예는 +1입니다. 그것들이 함수의 매개 변수라면 경고가 표시되지 않습니다. 또한 a진정으로 부호가 -a + b없을 수 있으면 a부정적이고 b긍정적 인 경우 계산을 쉽게 넘칠 수 있습니다 .
Patrick M

@PatrickM : 편집 참조. 정의되지 않은 동작은 컴파일러가 경고 발행을 포함하여 원하는대로 수행 할 수 있음을 의미합니다. 부호없는 산술은 UB를 생성하지 않지만 다음 2의 더 높은 거듭 제곱은 모듈로 감소합니다.
david.pfx 2014 년

(b+c)마지막 행 의 표현식 은 인수를로 승격 int하므로 컴파일러 int가 16 비트로 정의되지 않는 한 (고대이거나 작은 마이크로 컨트롤러를 대상으로 함) 마지막 행은 완벽하게 합법적입니다.
supercat

@ supercat : 그렇게 생각하지 않습니다. 결과의 일반적인 유형과 유형은 짧아야합니다. 만약 이것이 요구 된 것이 아니라면, 질문을 게시하고 싶습니까?
david.pfx

@ david.pfx : C 산술 승격의 규칙은 매우 명확합니다. 해당 유형이 모든 값을 나타낼 수없는 경우 int승격되는 int경우가 아니면 승격 보다 작은 모든 항목이 승격됩니다 unsigned int. 모든 정의 된 동작이 프로모션이 포함 된 것과 동일 할 경우 컴파일러는 프로모션을 생략 할 수 있습니다 . 16 비트 유형이 래핑 추상 대수 고리로 작동하는 시스템에서 (a + b) + c 및 a + (b + c)는 동일합니다. 경우 int오버 플로우에 갇혀있는 16 비트 타입이었다, 그러나, 다음의 경우가있을 것입니다 곳 표현 중 하나 ...
supercat

7

대괄호는 운영자 우선 순위의 순서를 조작하기위한 것입니다. 컴파일되면 괄호는 더 이상 런타임에 필요하지 않으므로 더 이상 존재하지 않습니다. 컴파일 과정은 여러분과 내가 필요로하는 모든 대괄호, 공백 및 기타 구문 설탕을 제거하고 모든 연산자를 컴퓨터가 실행하기 훨씬 쉬운 것으로 변경합니다.

그래서 당신과 내가 볼 수있는 곳 ...

  • "int a = ((1 + 2) + 3);"

... 컴파일러는 다음과 같은 것을 더 방출 할 수 있습니다.

  • 숯 [1] :: "a"
  • Int32 :: DeclareStackVariable ()
  • Int32 :: 0x00000001
  • Int32 :: 0x00000002
  • Int32 :: Add ()
  • Int32 :: 0x00000003
  • Int32 :: Add ()
  • Int32 :: AssignToVariable ()
  • void :: DiscardResult ()

프로그램은 처음부터 시작하여 차례로 각 명령을 실행하여 실행됩니다.
운영자 우선 순위는 이제 "선착순"입니다.
컴파일러는 원래 구문을 찢어 버리는 동안 컴파일러가 모든 것을 해결했기 때문에 모든 것이 강력하게 입력 되었습니다.

좋아, 그것은 당신과 내가 다루는 것들과 다르지 않지만 우리는 그것을 운영하지 않습니다!


4
원격으로 이와 같은 것을 생성하는 단일 C ++ 컴파일러는 없습니다. 일반적으로 실제 CPU 코드를 생성하며 어셈블리도이 코드를 보지 않습니다.
MSalters

3
여기서 의도는 작성된 코드와 컴파일 된 출력 간의 구조적 차이를 보여주기위한 것입니다. 여기에서도 대부분의 사람들은 실제 기계 코드 나 어셈블리를 읽을 수 없습니다
DHall

@MSalters Nitpicking clang은 LLVM ISA를 '같은 것'(스택 기반이 아닌 SSA)으로 취급하면 '같은 것'을 방출합니다. LLVM에 대한 JVM 백엔드를 작성할 수 있으며 JVM ISA (AFAIK)는 스택 기반 clang-> llvm-> JVM과 매우 유사합니다.
Maciej Piechotka

LLVM ISA에는 런타임 문자열 리터럴을 사용하여 이름 스택 변수를 정의하는 기능이 없다고 생각합니다 (처음 두 개의 명령 만). 이것은 런타임과 컴파일 타임을 심각하게 혼합하고 있습니다. 이 질문은 그 혼란에 관한 것이기 때문에 구별이 중요합니다.
MSalters

6

부동 소수점인지 여부에 따라 다릅니다.

  • 부동 소수점 산술 덧셈은 연관성이 없으므로 옵티마이 저는 연산을 재정렬 할 수 없습니다 (fastmath 컴파일러 스위치를 추가하지 않는 한).

  • 정수 연산에서는 순서를 바꿀 수 있습니다.

귀하의 예제에서 둘 다 정확히 동일한 코드로 컴파일되기 때문에 정확히 동시에 실행됩니다 (추가는 왼쪽에서 오른쪽으로 평가됨).

그러나 java 및 C #조차도 최적화 할 수 있으며 런타임에 수행합니다.


부동 소수점 연산이 연관되어 있지 않다는 +1
Doval

질문의 예에서 괄호는 기본 (왼쪽) 연관성을 변경하지 않으므로이 점은 무의미합니다.
Marc van Leeuwen

1
마지막 문장에 대해서는 그렇게 생각하지 않습니다. java a와 c #에서 컴파일러는 최적화 된 바이트 코드 / IL을 생성합니다. 런타임은 영향을받지 않습니다.
Stefano Altieri

IL은 이러한 종류의 표현식에서는 작동하지 않으며, 명령어는 스택에서 특정 수의 값을 가져와 특정 수의 값 (일반적으로 0 또는 1)을 스택에 반환합니다. C #에서 런타임에 최적화되는 이런 종류의 이야기는 말도 안됩니다.
존 한나

6

일반적인 C ++ 컴파일러는 C ++ 자체가 아닌 머신 코드로 변환됩니다 . 그것은 쓸모없는 parens를 제거합니다. 예. 완료되면 parens가 없기 때문입니다. 머신 코드는 그런 식으로 작동하지 않습니다.



1

아니요,하지만 가능할 수도 있지만 다른 방법 일 수도 있습니다.

사람들이 이미 지적했듯이 (C, C ++, C # 또는 Java와 같이 추가가 왼쪽 연관 언어 인 경우) 표현 ((1 + 2) + 3)은 정확히와 같습니다 1 + 2 + 3. 소스 코드에서 무언가를 작성하는 다른 방법이므로 결과 기계 코드 또는 바이트 코드에 영향을 미치지 않습니다.

어느 쪽이든 결과는 두 개의 레지스터를 추가 한 다음 세 번째를 추가하거나 스택에서 두 개의 값을 가져 와서 스택에서 추가 한 다음 다시 가져 와서 다른 레지스터를 추가하거나 3 개의 레지스터를 추가하는 명령이됩니다. 다음 단계에서 가장 현명한 것 (머신 코드 또는 바이트 코드)에 따라 단일 조작 또는 세 가지 숫자를 합산하는 다른 방법. 바이트 코드의 경우, 그와 비슷한 IL 구조 (예를 들어 스택에 대한 일련의로드, 결과를 추가 및 푸시하기 위해 쌍을 팝핑)와 유사한 구조 변경이 발생할 수 있습니다. 머신 코드 레벨에서 해당 로직을 직접 복사하지는 않지만 해당 머신에 더 적합한 것을 생성합니다.

그러나 귀하의 질문에 더 많은 것이 있습니다.

제정신 C, C ++, Java 또는 C # 컴파일러의 경우, 두 명령문의 결과가 다음과 정확히 동일한 결과를 기대합니다.

int a = 6;

결과 코드가 리터럴에서 수학을 수행하는 데 시간을 낭비해야하는 이유는 무엇입니까? 프로그램의 상태에 대한 변경 사항은 결과를 멈추지 않을 것 1 + 2 + 36코드에서 이동해야하는지의 실행되고 그래서. 실제로, 어쩌면 그조차도 (어떻게 6으로 무엇을 하느냐에 따라, 우리는 모든 것을 버릴 수 있습니다. 그리고 C #조차도 "지터가 이것을 최적화하기 때문에 크게 최적화하지 마십시오"라는 철학을 가지고 있습니다. int a = 6불필요하게 모든 것을 버리는 것과 동등 하거나 버립니다).

그럼에도 불구하고 이것은 우리가 당신의 질문을 확장 할 수있게합니다. 다음을 고려하세요:

int a = (b - 2) / 2;
/* or */
int a = (b / 2)--;

int c;
if(d < 100)
  c = 0;
else
  c = d * 31;
/* or */
int c = d < 100 ? 0 : d * 32 - d
/* or */
int c = d < 100 && d * 32 - d;
/* or */
int c = (d < 100) * (d * 32 - d);

(이 마지막 두 예제는 유효한 C #이 아니며 여기의 다른 모든 것이 있으며 C, C ++ 및 Java에서 유효합니다.)

여기서도 출력 측면에서 정확히 동등한 코드가 있습니다. 상수 표현식이 아니기 때문에 컴파일 타임에 계산되지 않습니다. 한 양식이 다른 양식보다 빠를 수 있습니다. 어느 것이 더 빠릅니까? 그것은 프로세서와 아마도 상태의 다소 임의적 인 차이에 달려 있습니다 (특히 더 빠르면 훨씬 빠르지 않기 때문에).

그리고 그들은 개념적으로 수행 되는 순서의 차이에 관한 것이기 때문에 귀하의 질문과 전혀 관련이 없습니다 .

그들 각각에는 하나가 다른 것보다 빠를 것이라고 의심 할만한 이유가 있습니다. 단일 감소에는 특수 명령어가있을 (b / 2)--수 있으므로 실제로보다 빠를 수 있습니다 (b - 2) / 2. d * 32보다 빠르게 d << 5만들어서 d * 32 - d더 빠르게 생산할 수 있습니다 d * 31. 마지막 두 가지의 차이점이 특히 흥미 롭습니다. 하나는 어떤 경우에는 일부 처리를 건너 뛸 수 있지만, 다른 하나는 분기 잘못 예측의 가능성을 피합니다.

따라서 이것은 우리에게 두 가지 질문을 남깁니다. 1. 하나는 실제로 다른 것보다 빠릅니까? 2. 컴파일러는 느리게 변환할까요?

답은 1입니다. 2. 아마도.

또는 확장하려면 문제의 프로세서에 의존하기 때문에 다릅니다. 하나의 나이브 머신 코드가 다른 머신의 나이브 머신 코드보다 빠를 수있는 프로세서가 분명히 존재합니다. 전자 컴퓨팅의 역사에서, 항상 더 빠른 것은 없었습니다. (특히 분기 잘못 예측 요소는 파이프 라인되지 않은 CPU가 더 일반적 일 때 많은 것과 관련이 없었습니다).

그리고 아마도 컴파일러 (및 지터 및 스크립트 엔진)가 수행 할 수있는 다양한 최적화가 있기 때문에 어떤 경우에는 일부가 의무화 될 수 있지만 일반적으로 논리적으로 동등한 일부 코드를 찾을 수 있습니다 가장 순진한 컴파일러조차도 정확히 동일한 결과와 논리적으로 동등한 일부 코드를 가지고 있으며, 가장 정교하게도 다른 코드보다 더 빠른 코드를 생성합니다.

아주 작은 최적화 문제인 것 같습니다.

아니요. 여기에 제시 한 것보다 더 복잡한 차이점이 있더라도 최적화와는 전혀 관련이없는 매우 중요한 문제인 것 같습니다. 어쨌든 읽기 어려운 것이 읽기 ((1 + 2) + 3쉬운 것보다 느릴 수 있기 때문에 비관 화의 문제입니다 1 + 2 + 3.

그러나 C # / Java / ...보다 C ++를 선택하는 것은 최적화 (IMHO)에 관한 것입니다.

이것이 C # 또는 Java 대신 C ++을 선택하는 것이 "모든 것"이라면 사람들은 Stroustrup 및 ISO / IEC 14882의 사본을 구워 C ++ 컴파일러의 공간을 확보하여 더 많은 MP3 또는 기타 공간을 확보해야한다고 말하고 싶습니다.

이 언어들은 서로 다른 장점이 있습니다.

그중 하나는 C ++이 여전히 일반적으로 메모리 사용에서 더 빠르고 가볍다는 것입니다. 예, C # 및 / 또는 Java가 더 빠르거나 응용 프로그램 수명 시간 동안 메모리를 더 잘 사용하는 예가 있으며 관련 기술이 향상됨에 따라 더 일반적으로 사용되고 있지만 C ++로 작성된 평균 프로그램은 여전히 두 언어 중 하나에 비해 작업 속도가 빠르고 메모리를 적게 사용하는 작은 실행 파일

최적화되지 않았습니다.

최적화 는 때때로 "일이 더 빨라진다"는 의미로 사용됩니다. 우리가 실제로 "최적화"에 관해 이야기 할 때 실제로 일을 더 빨리 진행시키는 것에 대해 이야기하고 있기 때문에 이해할 수 있습니다. 그래서 하나는 다른 하나의 속기가되었으므로 나는 그런 식으로 자신을 그렇게 잘못 사용한다는 것을 인정할 것입니다.

"일이 더 빨라진다"는 올바른 단어는 최적화 가 아닙니다 . 여기서 올바른 단어는 개선 입니다. 프로그램을 변경하고 의미있는 유일한 차이점이 이제 더 빠르다는 것이 어떤 식 으로든 최적화되지 않았다면 더 좋습니다.

최적화 는 특정 측면 및 / 또는 특정 경우와 관련하여 개선 할 때입니다. 일반적인 예는 다음과 같습니다.

  1. 한 사용 사례에서는 더 빠르지 만 다른 사용 사례에서는 느립니다.
  2. 더 빨라졌지만 더 많은 메모리를 사용합니다.
  3. 이제 메모리는 가벼워 지지만 느려집니다.
  4. 이제는 더 빠르지 만 유지 관리가 어렵습니다.
  5. 유지 관리가 쉬워졌지만 속도가 느려졌습니다.

이러한 경우는 다음과 같은 경우에 정당화됩니다.

  1. 더 빠른 유스 케이스는 더 일반적이거나 더 심각하게 방해받습니다.
  2. 프로그램이 너무 느려서 RAM이 부족합니다.
  3. 프로그램은 매우 빠른 RAM을 사용하여 초고속 처리를 실행하는 것보다 스와핑에 더 많은 시간을 소비했기 때문에 중단되었습니다.
  4. 이 프로그램은 용납 할 수 없을 정도로 느리고 코드를 이해하기가 어렵습니다.
  5. 이 프로그램은 여전히 ​​허용 가능하며 이해하기 쉬운 코드베이스는 유지 관리 비용이 저렴하고 다른 개선 작업을보다 쉽게 ​​수행 할 수 있습니다.

그러나 이러한 경우는 다른 시나리오에서도 정당화되지 않을 것입니다. 코드는 절대적으로 절대적인 품질 측정으로 개선되지 않았으며, 특정 용도에보다 적합하도록 특정 측면에서 개선되었습니다. 최적화되었습니다.

언어 선택은 여기에 영향을 미칩니다. 속도, 메모리 사용 및 가독성이 모두 영향을받을 수 있지만 다른 시스템과의 호환성, 라이브러리의 가용성, 런타임 가용성, 주어진 운영 체제에서의 런타임의 성숙도도 마찬가지입니다. (내 죄로 인해 나는 리눅스와 안드로이드를 내가 가장 좋아하는 OS로, C #을 내가 가장 좋아하는 언어로, 결국 모노는 훌륭하지만 여전히 이것에 대해 상당히 반대합니다).

"C # / Java / ...보다 C ++를 선택하는 것은 최적화에 관한 것"이라고 말하는 것은 C ++이 정말 짜증나게 생각하는 경우에만 의미가 있습니다. C ++ 자체에도 불구하고 더 낫다고 생각되면 마지막으로 필요한 마이크로 옵트에 대해 걱정해야합니다. 실제로, 당신은 아마 그것을 포기하는 것이 더 나을 것입니다. 행복한 해커도 최적화 할 수있는 품질입니다!

그러나 "C ++을 좋아하고 내가 좋아하는 것 중 하나가 여분의 사이클을 짜내는 것"이라고 말하는 경향이 있다면, 그것은 다른 문제입니다. micro-opts가 재귀 습관 일 수있는 경우에만 가치가있는 경우입니다 (즉, 자연스럽게 코딩하는 방식이 느릴수록 더 빠를 것입니다). 그렇지 않으면 그들은 조기 최적화조차하지 않으며, 상황을 악화시키는 조기 비관 화입니다.


0

순서 식을 평가해야하는 컴파일러를 알려주는 괄호가 있습니다. 어쨌든 사용되는 순서를 지정하기 때문에 때로는 유용하지 않습니다 (가독성을 향상 시키거나 악화시키는 경우 제외). 때때로 그들은 순서를 바꿉니다. 에

int a = 1 + 2 + 3;

실제로 존재하는 모든 언어에는 1 + 2를 더한 다음 결과에 3을 더하여 합계를 계산하는 규칙이 있습니다.

int a = 1 + (2 + 3);

괄호는 다른 순서를 강제합니다. 먼저 2 + 3을 더한 다음 1에 결과를 더합니다. 괄호 예제는 어쨌든 생성 된 것과 동일한 순서를 생성합니다. 이제이 예제에서 연산 순서는 약간 다르지만 정수 덧셈이 작동하는 방식은 결과가 동일합니다. 에

int a = 10 - (5 - 4);

괄호는 중요합니다. 제외하면 결과가 9에서 1로 변경됩니다.

컴파일러가 어떤 작업이 어떤 순서로 수행되는지 결정한 후 괄호는 완전히 잊혀집니다. 이 시점에서 컴파일러가 기억하는 것은 어떤 작업을 어떤 순서로 수행 할 것인가입니다. 따라서 실제로 컴파일러가 여기서 최적화 할 수있는 것은 없으며 괄호는 사라졌습니다 .


practically every language in existence; APL 제외 : (1-2)+3(2), 1-(2+3)(-4) 및 1-2+3( -4)를 입력하십시오 (여기) [tryapl.org] .
tomsmeding

0

나는 말한 것의 많은 것에 동의하지만… 여기서 중요한 것은 괄호가 연산 순서를 강제하기 위해 있다는 것입니다 ... 컴파일러가 절대적으로하고 있습니다. 그렇습니다. 머신 코드를 생성하지만… 요점이 아니며 요청되는 것이 아닙니다.

괄호는 실제로 사라졌습니다. 이미 말했듯이, 기계 코드의 일부가 아니며 숫자이며 다른 것은 아닙니다. 어셈블리 코드는 기계 코드가 아니며, 사람이 읽을 수 있으며, 명령어는 opcode가 아니라 이름으로 포함되어 있습니다. 기계는 opcodes-어셈블리 언어의 숫자 표현을 실행합니다.

Java와 같은 언어는 언어를 생성하는 시스템에서 부분적으로 만 컴파일되므로 중간 영역에 속합니다. 그것들은 그것을 실행하는 기계에서 특정 코드를 가공하도록 컴파일되었지만이 질문과 아무런 차이가 없습니다. 괄호는 여전히 첫 번째 컴파일 후에 사라집니다.


1
이것이 질문에 대한 대답인지 확실하지 않습니다. 지지하는 단락은 도움이되는 것보다 더 혼란 스럽다. Java 컴파일러는 C ++ 컴파일러와 어떤 관련이 있습니까?
Adam Zuckerman

OP는 괄호가 사라 졌는지 물었다. 나는 실행 코드가 단지 opcode를 나타내는 숫자라고 말했다. Java는 또 다른 대답으로 제기되었습니다. 나는 그것이 질문에 잘 대답한다고 생각합니다 ... 그러나 그것은 단지 내 의견입니다. 답장을 보내 주셔서 감사합니다.
jinzai

3
괄호는 "작업 순서"를 강요하지 않습니다. 우선 순위를 변경합니다. 그래서에서 a = f() + (g() + h());컴파일러는 무료이며, 호출 f, g그리고 h순서대로 (또는 임의의 순서로는 기쁘게).
Alok

나는 그 진술에 동의하지 않는다… 당신은 절대적으로 괄호로 순서를 강요 할 수있다.
jinzai 2016

0

언어에 관계없이 컴파일러는 모든 접두사 수학을 접미사로 변환합니다. 다시 말해, 컴파일러가 다음과 같은 것을 볼 때

((a+b)+c)

그것을 다음과 같이 번역합니다.

 a b + c +

이것은 사람들이 읽기 쉬운 접두어 표기법을 사용하는 반면, 접두어 표기법은 컴퓨터가 작업을 수행하기 위해 수행해야하는 실제 단계에 훨씬 가깝고 이미 잘 개발 된 알고리즘이 있기 때문입니다. 정의에서 postfix는 연산 순서 또는 괄호와 관련된 모든 문제를 제거하여 실제로 기계 코드를 작성할 때 작업을 훨씬 쉽게 만듭니다.

나는 추천 역 폴란드 표기법에 위키 피 디아 기사를 주제에 대한 자세한 내용은.


5
컴파일러가 연산을 번역하는 방법에 대한 잘못된 가정입니다. 예를 들어 여기에서 스택 머신을 가정합니다. 벡터 프로세서가 있다면 어떨까요? 레지스터가 많은 머신을 가지고 있다면 어떨까요?
Ahmed Masud
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.