코드가
int a = ((1 + 2) + 3); // Easy to read
보다 느리게 달리다
int a = 1 + 2 + 3; // (Barely) Not quite so easy to read
또는 "불필요한"괄호를 제거 / 최적화 할만큼 현명한 컴파일러가 영리한가?
매우 작은 최적화 문제처럼 보이지만 C # / Java / ... 대신 C ++를 선택하는 것은 최적화 (IMHO)에 관한 것입니다.
코드가
int a = ((1 + 2) + 3); // Easy to read
보다 느리게 달리다
int a = 1 + 2 + 3; // (Barely) Not quite so easy to read
또는 "불필요한"괄호를 제거 / 최적화 할만큼 현명한 컴파일러가 영리한가?
매우 작은 최적화 문제처럼 보이지만 C # / Java / ... 대신 C ++를 선택하는 것은 최적화 (IMHO)에 관한 것입니다.
답변:
컴파일러는 실제로 괄호를 삽입하거나 제거하지 않습니다. 표현식에 해당하는 구문 분석 트리 (괄호가없는)를 작성하기 때문에 작성한 괄호를 존중해야합니다. 표현을 완전히 괄호로 묶으면 구문 분석 트리가 무엇인지 인간에게 즉시 알 수 있습니다. 과도하게 중복 된 괄호를 극단으로 가면 int a = (((0)));
결과 구문 분석 트리 (및 생성 된 코드를 변경하지 않고 파서의 일부 사이클을 낭비하면서 판독기의 뉴런에 쓸모없는 스트레스를 유발합니다 ) 가장 작은 비트.
괄호 를 쓰지 않으면 구문 분석기는 구문 분석 트리를 작성하는 작업을 계속 수행해야하며 연산자 우선 순위 및 연관성 규칙에 따라 구문 분석 트리를 작성해야합니다. 이러한 규칙은 컴파일러에 코드에 삽입 해야 할 (암시 적) 괄호를 알려주는 것으로 간주 할 수 있지만 파서는이 경우 괄호를 실제로 처리하지는 않지만 괄호처럼 동일한 구문 분석 트리를 생성하도록 구성되었습니다. 특정 장소에있었습니다. int a = (1+2)+3;
(의 연관성은 +
왼쪽에 있음) 에서 와 같이 정확하게 그 장소에 괄호를 넣으면 파서는 약간 다른 경로로 동일한 결과에 도달합니다. 에서와 같이 다른 괄호를 넣으면int a = 1+(2+3);
그런 다음 다른 구문 분석 트리를 강제 실행하여 다른 코드가 생성 될 수 있습니다 ( 결과 코드 를 실행 하는 효과 가 결코 다르지 않는 한 컴파일러가 구문 분석 트리를 작성한 후 변환을 적용 할 수 있기 때문에 그렇지 않을 수도 있음) 그것). 생성 코드에 차이가 있다고 가정하면 일반적으로 어느 것이 더 효율적인지에 대해서는 말할 수 없습니다. 가장 중요한 요점은 물론 대부분의 경우 구문 분석 트리가 수학적으로 동등한 표현을 제공하지 않으므로 실행 속도를 비교하는 것이 요점을 벗어납니다. 표현을 작성하면 적절한 결과를 얻을 수 있습니다.
결과는 다음과 같습니다. 정확성과 가독성을 위해 필요에 따라 괄호를 사용하십시오. 중복되는 경우 실행 속도에 전혀 영향을 미치지 않으며 컴파일 시간에는 무시할만한 영향을 미칩니다.
그리고이 중 어느 것도 최적화와 관련 이 없으며 , 이는 구문 분석 트리가 작성된 후에 발생하므로 구문 분석 트리가 어떻게 구성되었는지 알 수 없습니다. 이것은 가장 오래되고 어리석은 컴파일러에서 가장 똑똑하고 가장 현대적인 컴파일러로 변경하지 않고 적용됩니다. 해석 언어 ( "컴파일 시간"과 "실행 시간"이 일치하는 경우)에서만 중복 괄호에 대한 패널티가 부과 될 수 있지만 대부분의 이러한 언어는 적어도 구문 분석 단계가 한 번만 수행되도록 구성되어 있다고 생각합니다. 각 문장마다 (구문 분석 된 형태로 실행을 위해 저장).
a = b + c * d;
, a = b + (c * d);
것 [무해한] 중복 괄호. 그들이 코드를 더 읽기 쉽게 만드는 데 도움이된다면 좋습니다. a = (b + c) * d;
중복되지 않는 괄호입니다. 실제로 결과 구문 분석 트리를 변경하고 다른 결과를 제공합니다. 완벽하게 합법적이지만 (실제로 필요하지만) 암시 적 기본 그룹화와 동일하지 않습니다.
괄호는 컴파일러가 아닌 귀하의 이익을 위해서만 있습니다. 컴파일러는 명령문을 나타내는 올바른 기계 코드를 작성합니다.
참고로, 컴파일러는 가능한 경우 완전히 최적화 할 수있을 정도로 영리합니다. 귀하의 예에서, 이것은 int a = 6;
컴파일 타임에 설정됩니다.
int a = 8;// = 2*3 + 5
int five = 7; //HR made us change this to six...
실제로 질문 한 질문에 대한 대답은 '아니요'이지만 질문하려는 질문에 대한 대답은 '예'입니다. 괄호를 추가해도 코드 속도가 느려지지 않습니다.
최적화에 대해 질문했지만 괄호는 최적화와 관련이 없습니다. 컴파일러는 생성 된 코드의 크기 나 속도를 개선하기 위해 다양한 최적화 기술을 적용합니다 (때로는 둘 다). 예를 들어, 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)으로 평가 될 때 발생합니다. 이것은이 답변의 범위를 벗어난 복잡한 주제입니다.
a
진정으로 부호가 -a + b
없을 수 있으면 a
부정적이고 b
긍정적 인 경우 계산을 쉽게 넘칠 수 있습니다 .
(b+c)
마지막 행 의 표현식 은 인수를로 승격 int
하므로 컴파일러 int
가 16 비트로 정의되지 않는 한 (고대이거나 작은 마이크로 컨트롤러를 대상으로 함) 마지막 행은 완벽하게 합법적입니다.
int
승격되는 int
경우가 아니면 승격 보다 작은 모든 항목이 승격됩니다 unsigned int
. 모든 정의 된 동작이 프로모션이 포함 된 것과 동일 할 경우 컴파일러는 프로모션을 생략 할 수 있습니다 . 16 비트 유형이 래핑 추상 대수 고리로 작동하는 시스템에서 (a + b) + c 및 a + (b + c)는 동일합니다. 경우 int
오버 플로우에 갇혀있는 16 비트 타입이었다, 그러나, 다음의 경우가있을 것입니다 곳 표현 중 하나 ...
대괄호는 운영자 우선 순위의 순서를 조작하기위한 것입니다. 컴파일되면 괄호는 더 이상 런타임에 필요하지 않으므로 더 이상 존재하지 않습니다. 컴파일 과정은 여러분과 내가 필요로하는 모든 대괄호, 공백 및 기타 구문 설탕을 제거하고 모든 연산자를 컴퓨터가 실행하기 훨씬 쉬운 것으로 변경합니다.
그래서 당신과 내가 볼 수있는 곳 ...
... 컴파일러는 다음과 같은 것을 더 방출 할 수 있습니다.
프로그램은 처음부터 시작하여 차례로 각 명령을 실행하여 실행됩니다.
운영자 우선 순위는 이제 "선착순"입니다.
컴파일러는 원래 구문을 찢어 버리는 동안 컴파일러가 모든 것을 해결했기 때문에 모든 것이 강력하게 입력 되었습니다.
좋아, 그것은 당신과 내가 다루는 것들과 다르지 않지만 우리는 그것을 운영하지 않습니다!
부동 소수점인지 여부에 따라 다릅니다.
부동 소수점 산술 덧셈은 연관성이 없으므로 옵티마이 저는 연산을 재정렬 할 수 없습니다 (fastmath 컴파일러 스위치를 추가하지 않는 한).
정수 연산에서는 순서를 바꿀 수 있습니다.
귀하의 예제에서 둘 다 정확히 동일한 코드로 컴파일되기 때문에 정확히 동시에 실행됩니다 (추가는 왼쪽에서 오른쪽으로 평가됨).
그러나 java 및 C #조차도 최적화 할 수 있으며 런타임에 수행합니다.
아니요,하지만 가능할 수도 있지만 다른 방법 일 수도 있습니다.
사람들이 이미 지적했듯이 (C, C ++, C # 또는 Java와 같이 추가가 왼쪽 연관 언어 인 경우) 표현 ((1 + 2) + 3)
은 정확히와 같습니다 1 + 2 + 3
. 소스 코드에서 무언가를 작성하는 다른 방법이므로 결과 기계 코드 또는 바이트 코드에 영향을 미치지 않습니다.
어느 쪽이든 결과는 두 개의 레지스터를 추가 한 다음 세 번째를 추가하거나 스택에서 두 개의 값을 가져 와서 스택에서 추가 한 다음 다시 가져 와서 다른 레지스터를 추가하거나 3 개의 레지스터를 추가하는 명령이됩니다. 다음 단계에서 가장 현명한 것 (머신 코드 또는 바이트 코드)에 따라 단일 조작 또는 세 가지 숫자를 합산하는 다른 방법. 바이트 코드의 경우, 그와 비슷한 IL 구조 (예를 들어 스택에 대한 일련의로드, 결과를 추가 및 푸시하기 위해 쌍을 팝핑)와 유사한 구조 변경이 발생할 수 있습니다. 머신 코드 레벨에서 해당 로직을 직접 복사하지는 않지만 해당 머신에 더 적합한 것을 생성합니다.
그러나 귀하의 질문에 더 많은 것이 있습니다.
제정신 C, C ++, Java 또는 C # 컴파일러의 경우, 두 명령문의 결과가 다음과 정확히 동일한 결과를 기대합니다.
int a = 6;
결과 코드가 리터럴에서 수학을 수행하는 데 시간을 낭비해야하는 이유는 무엇입니까? 프로그램의 상태에 대한 변경 사항은 결과를 멈추지 않을 것 1 + 2 + 3
인 6
코드에서 이동해야하는지의 실행되고 그래서. 실제로, 어쩌면 그조차도 (어떻게 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 ++로 작성된 평균 프로그램은 여전히 두 언어 중 하나에 비해 작업 속도가 빠르고 메모리를 적게 사용하는 작은 실행 파일
최적화되지 않았습니다.
최적화 는 때때로 "일이 더 빨라진다"는 의미로 사용됩니다. 우리가 실제로 "최적화"에 관해 이야기 할 때 실제로 일을 더 빨리 진행시키는 것에 대해 이야기하고 있기 때문에 이해할 수 있습니다. 그래서 하나는 다른 하나의 속기가되었으므로 나는 그런 식으로 자신을 그렇게 잘못 사용한다는 것을 인정할 것입니다.
"일이 더 빨라진다"는 올바른 단어는 최적화 가 아닙니다 . 여기서 올바른 단어는 개선 입니다. 프로그램을 변경하고 의미있는 유일한 차이점이 이제 더 빠르다는 것이 어떤 식 으로든 최적화되지 않았다면 더 좋습니다.
최적화 는 특정 측면 및 / 또는 특정 경우와 관련하여 개선 할 때입니다. 일반적인 예는 다음과 같습니다.
이러한 경우는 다음과 같은 경우에 정당화됩니다.
그러나 이러한 경우는 다른 시나리오에서도 정당화되지 않을 것입니다. 코드는 절대적으로 절대적인 품질 측정으로 개선되지 않았으며, 특정 용도에보다 적합하도록 특정 측면에서 개선되었습니다. 최적화되었습니다.
언어 선택은 여기에 영향을 미칩니다. 속도, 메모리 사용 및 가독성이 모두 영향을받을 수 있지만 다른 시스템과의 호환성, 라이브러리의 가용성, 런타임 가용성, 주어진 운영 체제에서의 런타임의 성숙도도 마찬가지입니다. (내 죄로 인해 나는 리눅스와 안드로이드를 내가 가장 좋아하는 OS로, C #을 내가 가장 좋아하는 언어로, 결국 모노는 훌륭하지만 여전히 이것에 대해 상당히 반대합니다).
"C # / Java / ...보다 C ++를 선택하는 것은 최적화에 관한 것"이라고 말하는 것은 C ++이 정말 짜증나게 생각하는 경우에만 의미가 있습니다. C ++ 자체에도 불구하고 더 낫다고 생각되면 마지막으로 필요한 마이크로 옵트에 대해 걱정해야합니다. 실제로, 당신은 아마 그것을 포기하는 것이 더 나을 것입니다. 행복한 해커도 최적화 할 수있는 품질입니다!
그러나 "C ++을 좋아하고 내가 좋아하는 것 중 하나가 여분의 사이클을 짜내는 것"이라고 말하는 경향이 있다면, 그것은 다른 문제입니다. micro-opts가 재귀 습관 일 수있는 경우에만 가치가있는 경우입니다 (즉, 자연스럽게 코딩하는 방식이 느릴수록 더 빠를 것입니다). 그렇지 않으면 그들은 조기 최적화조차하지 않으며, 상황을 악화시키는 조기 비관 화입니다.
순서 식을 평가해야하는 컴파일러를 알려주는 괄호가 있습니다. 어쨌든 사용되는 순서를 지정하기 때문에 때로는 유용하지 않습니다 (가독성을 향상 시키거나 악화시키는 경우 제외). 때때로 그들은 순서를 바꿉니다. 에
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] .
나는 말한 것의 많은 것에 동의하지만… 여기서 중요한 것은 괄호가 연산 순서를 강제하기 위해 있다는 것입니다 ... 컴파일러가 절대적으로하고 있습니다. 그렇습니다. 머신 코드를 생성하지만… 요점이 아니며 요청되는 것이 아닙니다.
괄호는 실제로 사라졌습니다. 이미 말했듯이, 기계 코드의 일부가 아니며 숫자이며 다른 것은 아닙니다. 어셈블리 코드는 기계 코드가 아니며, 사람이 읽을 수 있으며, 명령어는 opcode가 아니라 이름으로 포함되어 있습니다. 기계는 opcodes-어셈블리 언어의 숫자 표현을 실행합니다.
Java와 같은 언어는 언어를 생성하는 시스템에서 부분적으로 만 컴파일되므로 중간 영역에 속합니다. 그것들은 그것을 실행하는 기계에서 특정 코드를 가공하도록 컴파일되었지만이 질문과 아무런 차이가 없습니다. 괄호는 여전히 첫 번째 컴파일 후에 사라집니다.
a = f() + (g() + h());
컴파일러는 무료이며, 호출 f
, g
그리고 h
순서대로 (또는 임의의 순서로는 기쁘게).
언어에 관계없이 컴파일러는 모든 접두사 수학을 접미사로 변환합니다. 다시 말해, 컴파일러가 다음과 같은 것을 볼 때
((a+b)+c)
그것을 다음과 같이 번역합니다.
a b + c +
이것은 사람들이 읽기 쉬운 접두어 표기법을 사용하는 반면, 접두어 표기법은 컴퓨터가 작업을 수행하기 위해 수행해야하는 실제 단계에 훨씬 가깝고 이미 잘 개발 된 알고리즘이 있기 때문입니다. 정의에서 postfix는 연산 순서 또는 괄호와 관련된 모든 문제를 제거하여 실제로 기계 코드를 작성할 때 작업을 훨씬 쉽게 만듭니다.
나는 추천 역 폴란드 표기법에 위키 피 디아 기사를 주제에 대한 자세한 내용은.