'for'루프에서 1 씩 증가 할 때 포맷팅 뒤에 기술적 인 이유는 무엇입니까?
웹 전체에서 코드 샘플에는 for
다음과 같은 루프 가 있습니다.
for(int i = 0; i < 5; i++)
다음 형식을 사용하는 동안 :
for(int i = 0; i != 5; ++i)
더 효율적이라고 믿기 때문에이 작업을 수행하지만 대부분의 경우 실제로 중요합니까?
모두가 마이크로 최적화를 좋아하지만, 내가 볼 수있는 한 차이를 만들지는 못할 것입니다. 고급 최적화없이 인텔 프로세서 용 g ++로 두 가지 변형을 컴파일했으며 그 결과는
for(int i = 0; i < 5; i++)
movl $0, -12(%ebp)
jmp L2
L3:
leal -12(%ebp), %eax
incl (%eax)
L2:
cmpl $4, -12(%ebp)
jle L3
for(int i = 0; i != 5; ++i)
movl $0, -12(%ebp)
jmp L7
L8:
leal -12(%ebp), %eax
incl (%eax)
L7:
cmpl $5, -12(%ebp)
jne L8
내 생각 jle
과 jne
대부분의 아키텍쳐에 동등하게 빠른 지침을 번역해야한다. 따라서 성능을 위해 둘을 구별해서는 안됩니다. 일반적으로 첫 번째 것이 조금 더 안전하고 더 일반적이라고 생각합니다.
편집 (2 년 후) : 이 스레드가 최근에 다시 많은 관심을 받았기 때문에이 질문에 일반적으로 대답하기가 어려울 것이라고 덧붙이고 싶습니다. 더 효율적인 코드 버전은 특히 C-Standard [PDF]에 정의되어 있지 않습니다 (C ++ 및 C #에도 동일하게 적용됨).
5.1.2.3 절 프로그램 실행
§1 이 국제 표준의 의미 론적 설명은 최적화 문제가 관련이없는 추상 기계의 동작을 설명합니다.
그러나 현대 컴파일러가 똑같이 효율적인 코드를 생성한다고 가정하는 것이 합리적이며 매우 드문 경우에만 루프 테스트 와 계산 표현식 이 for 루프의 병목 현상이 될 것이라고 생각합니다 .
맛은 쓴다
for(int i = 0; i < 5; ++i)
어떤 이유로 i
루프에서 50으로 점프하면 버전이 영원히 반복됩니다. 은 i < 5
전성 검사입니다.
형태
for (int i = 0; i < 5; i++)
이다 관용적 그래서 숙련 된 C 프로그래머 읽기 쉽다. 특히 배열을 반복하는 데 사용할 때. 더 빨리 읽을 수 있으므로 가능하면 관용적 코드를 작성해야합니다.
또한 루프 내부에서 i를 수정하거나 1과 다른 증분을 사용하는 상황에서 조금 더 안전합니다. 그러나 이것은 사소한 것입니다. 루프를 신중하게 설계하고 어설 션을 추가하여 잘못된 가정을 조기에 포착하는 것이 가장 좋습니다.
증분 규칙이 약간 변경되면 즉시 무한 루프가 발생합니다. 나는 첫 번째 끝 조건을 훨씬 선호합니다.
언어에 따라 다릅니다.
C ++ 텍스트는 종종 두 번째 형식을 제안합니다.이 형식은 직접 비교 (! =) 할 수 있지만 크거나 작음 조건에서는 사용할 수없는 반복기와 함께 작동합니다. 또한 비교를 위해 변수의 사본이 필요하지 않기 때문에 사전 증가가 사후 증가보다 빠를 수 있지만 옵티마이 저는이를 처리 할 수 있습니다.
정수의 경우 두 형식 모두 작동합니다. C에 대한 일반적인 관용구는 첫 번째이고 C ++의 경우 두 번째입니다.
C # 및 Java 사용의 경우 foreach는 모든 것을 반복합니다.
C ++에는 std :: for_each 함수도 있습니다.이 함수는 간단한 경우에 여기에있는 예제보다 더 복잡 할 것이고 C # foreach처럼 보일 수 있지만 내부가 복잡 할 수있는 Boost FOR_EACH보다 더 복잡 할 것입니다.
i ++ 대신 ++ i를 사용하는 것과 관련하여 대부분의 컴파일러에서 차이를 만들지 않지만 반복기로 사용할 때 ++ i가 i ++보다 더 효율적일 수 있습니다.
실제로 당신이주는 것에 4 개의 순열이 있습니다. 두 사람에게 :
for(int i = 0; i < 5; i++)
for(int i = 0; i != 5; ++i)
다음을 추가 할 수 있습니다.
for(int i = 0; i < 5; ++i)
for(int i = 0; i != 5; i++)
최신 컴파일러를 사용하는 대부분의 최신 컴퓨터에서 이것이 정확히 동일한 효율성을 가질 것이라는 것은 놀라운 일이 아닙니다. 평등 비교와보다 작음 비교 사이에 차이가있는 작은 프로세서를위한 프로그래밍을 언젠가는 스스로 발견 할 수 있습니다.
어떤 경우에는 우리가 0과 5를 선택한 이유에 따라 "보다 작다"또는 "같지 않음"을 생각하는 특별한 경우가있는 특정 마음에 더 합리적 일 수 있지만, 그럼에도 불구하고 하나를 분명하게 만드는 이유 코더는 다른 것과 함께 할 수 없습니다.
더 추상적으로, 다음과 같은 형식이 있습니다.
for(someType i = start; i < end; i++)
for(someType i = start; i != end; ++i)
for(someType i = start; i < end; ++i)
for(someType i = start; i != end; i++)
여기서 명백한 차이점은 두 경우 someType
에는에 대한 의미가 <
있어야하고 나머지는에 대한 의미가 있어야한다는 것입니다 !=
. C ++ (및 STL 반복기와 동일한 접근 방식이 가능하고 때로는 유용하지만 관용적이지 않고 공통 라이브러리에서 직접 지원하지 않는 C #에서 잠재적으로 C #에서)를 포함하여 !=
정의되고 <
매우 일반적이지 않은 유형 보다 직접적인 지원을 제공하는 라이벌 관용구가 있기 때문에 유용합니다.) STL 접근 방식은 유효한 반복기 유형 집합 내에 포인터를 포함하도록 특별히 설계되었다는 점은 주목할 가치가 있습니다. STL을 사용하는 습관이 있다면 다음과 같은 양식을 고려할 것입니다.!=
정수에 적용될 때도 훨씬 더 관용적입니다. 개인적으로 극소량의 노출만으로도 본능적으로 만들 수있었습니다.
다른 한편으로 정의 <
및 정의 !=
는 드물지만 i
의 값 의 다른 증가로 증분을 대체 하거나 i
루프 내에서 변경 될 수있는 경우에 적용 할 수 있습니다.
따라서 양쪽 모두가 유일한 접근 방식 인 확실한 경우가 있습니다.
이제 ++i
대 i++
. 다시 정수를 사용하고 결과를 반환하는 함수를 통하지 않고 직접 호출하면 실제 결과는 정확히 동일합니다.
일부 C 스타일 언어 (연산자 오버로딩이없는 언어)에서는 정수와 포인터가 유일한 경우입니다. 우리는 인위적으로 인위적으로 증가가 함수를 통해 호출되는 경우를 발명 할 수 있으며 컴파일러가 어쨌든 계속 동일한 것으로 바꿀 가능성이 있습니다.
C ++ 및 C #을 사용하면이를 재정의 할 수 있습니다. 일반적으로 접두사 ++
는 다음을 수행하는 함수처럼 작동합니다.
val = OneMoreThan(val);//whatever OneMoreThan means in the context.
//note that we assigned something back to val here.
return val;
그리고 접미사 ++
는 다음과 같은 기능을합니다.
SomeType copy = Clone(val);
val = OneMoreThan(val);
return copy;
C ++와 C # 모두 위와 완벽하게 일치하지는 않지만 (고의적으로 의사 코드와 일치하지 않음) 두 경우 모두 사본이 생성되거나 두 개가 생성 될 수 있습니다. 이것은 비싸거나 비싸지 않을 수 있습니다. 피할 수 있거나 피할 수 없을 수도 있습니다 (C ++에서는 종종 반환하여 접두사 형식에 대해 this
그리고 void 를 반환하여 접두사에서 완전히 피할 수 있습니다 ). 최적화되지 않거나 최적화되지 않을 수 있지만 특정 경우 ++i
보다 수행하는 것이 더 효율적일 수 있습니다 i++
.
더 구체적으로 말하면를 사용하여 약간의 성능 향상 가능성이 있으며 ++i
큰 클래스를 사용하면 상당 할 수도 있지만 C ++에서 누군가를 재정 의하여 두 가지가 완전히 다른 의미를 갖도록하는 것은 일반적으로 불가능합니다. 이것은 반대입니다. 따라서 접미사보다 접두사를 선호하는 습관을 들이면 천분의 일에 한 번씩 개선을 얻을 수 있지만 잃지 않을 것이므로 C ++ 코더가 자주 사용하는 습관입니다.
요약하면, 귀하의 질문에 주어진 두 가지 경우에는 전혀 차이가 없지만 동일한 변형이있을 수 있습니다.
저는 !=
20 년 이상 전에 Dijkstra의 "A Discipline of Programming" 이라는 책을 읽은 후 사용하기로 전환했습니다 . 그의 저서에서 Dijkstra는 더 약한 연속 조건이 루프 구조에서 더 강력한 사후 조건으로 이어진다 는 것을 관찰했습니다 .
예를 들어 i
루프 후에 노출되도록 구성을 수정 i >= 5
하면 첫 번째 루프의 사후 조건은이고 두 번째 루프의 사후 조건은 훨씬 더 강합니다 i == 5
. 이것은 루프 불변, 사후 조건 및 가장 약한 전제 조건의 공식적인 측면에서 프로그램에 대해 추론하는 데 더 좋습니다.
나는 가독성에 대해 말한 것에 동의합니다. 관리자가 읽기 쉬운 코드를 갖는 것이 중요하지만, 누구든 사전 증가와 사후 증가를 모두 이해할 수 있기를 바랍니다.
즉, 간단한 테스트를 실행하여 4 개의 루프 중 어느 것이 가장 빠르게 실행되는지에 대한 확실한 데이터를 얻을 수 있다고 생각했습니다. 저는 javac 1.7.0으로 컴파일하는 평균 사양 컴퓨터를 사용하고 있습니다.
내 프로그램은 for 루프를 생성하여 아무것도없이 2,000,000 번 반복합니다 (for 루프에있는 모든 작업을 수행하는 데 걸리는 시간으로 흥미로운 데이터를 낭비하지 않도록). 위에서 제안한 네 가지 유형을 모두 사용하고 결과를 곱하여 평균을 얻기 위해 1000 번 반복합니다.
실제 코드는 다음과 같습니다.
public class EfficiencyTest
{
public static int iterations = 1000;
public static long postIncLessThan() {
long startTime = 0;
long endTime = 0;
startTime = System.nanoTime();
for (int i=0; i < 2000000; i++) {}
endTime = System.nanoTime();
return endTime - startTime;
}
public static long postIncNotEqual() {
long startTime = 0;
long endTime = 0;
startTime = System.nanoTime();
for (int i=0; i != 2000000; i++) {}
endTime = System.nanoTime();
return endTime - startTime;
}
public static long preIncLessThan() {
long startTime = 0;
long endTime = 0;
startTime = System.nanoTime();
for (int i=0; i < 2000000; ++i) {}
endTime = System.nanoTime();
return endTime - startTime;
}
public static long preIncNotEqual() {
long startTime = 0;
long endTime = 0;
startTime = System.nanoTime();
for (int i=0; i != 2000000; ++i) {}
endTime = System.nanoTime();
return endTime - startTime;
}
public static void analyseResults(long[] data) {
long max = 0;
long min = Long.MAX_VALUE;
long total = 0;
for (int i=0; i<iterations; i++) {
max = (max > data[i]) ? max : data[i];
min = (data[i] > min) ? min : data[i];
total += data[i];
}
long average = total/iterations;
System.out.print("max: " + (max) + "ns, min: " + (min) + "ns");
System.out.println("\tAverage: " + (average) + "ns");
}
public static void main(String[] args) {
long[] postIncLessThanResults = new long [iterations];
long[] postIncNotEqualResults = new long [iterations];
long[] preIncLessThanResults = new long [iterations];
long[] preIncNotEqualResults = new long [iterations];
for (int i=0; i<iterations; i++) {
postIncLessThanResults[i] = postIncLessThan();
postIncNotEqualResults[i] = postIncNotEqual();
preIncLessThanResults[i] = preIncLessThan();
preIncNotEqualResults[i] = preIncNotEqual();
}
System.out.println("Post increment, less than test");
analyseResults(postIncLessThanResults);
System.out.println("Post increment, inequality test");
analyseResults(postIncNotEqualResults);
System.out.println("Pre increment, less than test");
analyseResults(preIncLessThanResults);
System.out.println("Pre increment, inequality test");
analyseResults(preIncNotEqualResults);
}
}
잘못 복사했다면 죄송합니다!
결과는 저 i < maxValue
를 놀라게했습니다. 테스트 는 사전 또는 사후 증가를 사용하든 루프 당 약 1.39ms가 걸렸지 만 1.05ms i != maxValue
가 걸렸습니다. 그것은 당신이 그것을 보는 방법에 따라 24.5 % 절약 또는 32.5 % 시간 손실입니다.
물론, for 루프를 실행하는 데 걸리는 시간은 병목 현상이 아닐 수 있지만, 필요한 경우 드문 경우에 대해 알아두면 유용한 최적화 유형입니다.
그래도 여전히 테스트에 충실 할 것이라고 생각합니다!
편집하다
나뿐만 아니라 전을 감소시키는 테스트, 이것은 정말 걸리는 일 시간에 영향이없는 것으로 나타났습니다 - for (int i = 2000000; i != 0; i--)
와 for (int i = 0; i != 2000000; i++)
같은 수행 시간의 같은 길이을 모두 for (int i = 2000000; i > 0; i--)
하고 for (int i = 0; i < 2000000; i++)
.
일반적인 코드에서 당신과 함께 버전을 선호한다 !=
그것은 단지 당신을 필요로하기 때문에 연산자 i
로 동등하게-비교 그동안, <
버전으로 그것을 필요로 관계형-비교 . 후자는 전자보다 더 강력한 요구 사항입니다. 일반적으로 더 약한 요구 사항이 완벽하게 충분할 때 더 강력한 요구 사항을 피하는 것이 좋습니다.
그렇긴해도 특정 경우에 int i
둘 다 똑같이 잘 작동하고 성능에는 차이가 없습니다.
나는 이것을하지 않을 것이다 :
for(int i = 0; i != 5; ++i)
i! = 5는 내가 절대 5가되지 않을 가능성을 위해 그것을 열어 둡니다. 그것을 건너 뛰고 무한 루프 나 배열 접근 자 오류가 발생하는 것은 너무 쉽습니다.
++i
많은 사람들이 ++을 앞에 놓을 수 있다는 것을 알고 있지만 그렇지 않은 사람들이 많이 있습니다. 코드는 사람들이 읽을 수 있어야하며 코드를 더 빠르게 만들기위한 마이크로 최적화가 될 수 있지만 누군가가 코드를 수정하고 그 이유를 파악해야 할 때 추가적인 골칫거리는 가치가 없습니다.
나는 Douglas Crockford가 최고의 제안을 가지고 있다고 생각하며 ++ 또는 전혀 사용하지 않는 것입니다. 때때로 너무 혼란스러워 질 수 있습니다 (루프에 있지 않을 수도 있지만 확실히 다른 곳일 수 있습니다). i = i + 1이라고 쓰는 것도 쉽습니다. 그는 그저 빠져 나가는 것이 나쁜 습관이라고 생각합니다. 끔찍한 "최적화 된"코드를보고 동의합니다.
Crockford가 얻는 것은 그 운영자와 함께 사람들이 다음과 같은 것을 작성하도록 할 수 있다는 것입니다.
var x = 0;
var y = x++;
y = ++x * (Math.pow(++y, 2) * 3) * ++x;
alert(x * y);
// 답은 54btw입니다.
컴파일러는 일반적으로 가능할 때 코드를 최적화 할 수있을만큼 똑똑하기 때문에 이러한 경우 효율성에 신경을 쓰는 것은 좋은 생각이 아닙니다.
나는 안전이 중요한 시스템을위한 소프트웨어를 생산하는 회사에서 일했고, 규칙 중 하나는 루프가! = 대신 "<"로 끝나야한다는 것입니다. 그에 대한 몇 가지 좋은 이유가 있습니다.
제어 변수는 일부 hw 문제 또는 일부 메모리 침입으로 인해 더 높은 값으로 점프 할 수 있습니다.
유지 관리에서 루프 내에서 반복기 값을 늘리거나 "i + = 2"와 같은 작업을 수행하면 루프가 영원히 롤링됩니다.
어떤 이유로 이터레이터 유형이 "int"에서 "float"로 변경되면 (누군가가 그렇게하는 이유는 모르겠지만 어쨌든 ...) 부동 소수점에 대한 정확한 비교는 나쁜 습관입니다.
(안전에 중요한 시스템을위한 MISRA C ++ 코딩 표준은 또한 규칙 6-5-2에서 "! ="대신 "<"를 선호한다고 말합니다. 여기에 규칙 정의를 게시 할 수 있는지 모르겠습니다. MISRA는 유료 문서이기 때문입니다.)
++ i 또는 i ++에 대해서는 ++ i를 선호합니다. 기본 유형으로 작업 할 때는 차이가 없지만 STL 반복기를 사용하는 경우 사전 증가가 더 효율적입니다. 그래서 나는 그것에 익숙해지기 위해 항상 preincrement를 사용합니다.
이 질문이 조금 복잡해지면서 가장 유익한 답변을 나열하기로 결정했습니다.
DenverCoder8 의 벤치 마킹은 루카스에 의해 컴파일 된 루프 버전뿐만 아니라 약간의 인정을받을 만합니다 . Tim Gee 는 사전 및 사후 증분의 차이점을 보여 주었고 User377178 은 <및! =의 장단점을 강조했습니다. Tenacious Techhunter 는 일반적으로 루프 최적화에 대해 작성했으며 언급 할 가치가 있습니다.
여기에 내 상위 5 개 답변이 있습니다.
레코드의 경우 "for"루프에 해당하는 cobol은 다음과 같습니다.
PERFORM VARYING VAR1
FROM +1 BY +1
UNTIL VAR1 > +100
* SOME VERBOSE COBOL STATEMENTS HERE
END-PERFORM.
또는
PERFORM ANOTHER-PARAGRAPH
VARYING VAR2 BY +1
UNTIL TERMINATING-CONDITION
WITH TEST AFTER.
이것에 대한 많은 변형이 있습니다. COBOL에 장기간 노출 되어도 마음이 손상되지 않은 사람들의 주요 문제는 기본적으로 루프 변수가 증가하기 전, 루프의 본문이 시작되기 전에 루프의 맨 위에서 테스트가 수행 UNTIL
된다는 것을 의미 WHILE
합니다. 처리되었습니다. 당신 WITH TEST AFTER
은 그것을 적절하게 만들기 위해 " "가 필요합니다 UNTIL
.
두 번째는 읽기가 어렵습니다 ( "표준"관행이 전자 인 것처럼 보이기 때문에).
코드에 숫자 리터럴이 뿌려져 있습니까? 부끄러워 ...
다시 궤도로 돌아와서 Donald Knuth는
97 % 정도의 작은 효율성은 잊어야합니다. 조기 최적화는 모든 악의 근원입니다.
따라서 파싱하기가 더 쉬운 것으로 요약됩니다.
위의 두 가지를 모두 고려하면 다음 중 프로그래머가 구문 분석하기 더 쉬운 것은 무엇입니까?
for (int i = 0; i < myArray.Length; ++i)
for (int i = 0; i != myArray.Length; ++i)
편집 : C #의 배열이 System.Collections.IList 인터페이스를 구현한다는 것을 알고 있지만 다른 언어에서는 반드시 그렇지는 않습니다.
가독성에 관해서. Ruby를 좋아하는 C # 프로그래머로서 최근에 다음 구문을 허용하는 int에 대한 확장 메서드를 작성했습니다 (Ruby에서와 같이).
4.Times(x => MyAction(x));
두 옵션의 장단점을 요약하려면
! =의 장점
- int가 일부 반복자 또는 템플릿 인수를 통해 전달 된 유형으로 대체되면 작동 할 가능성이 더 높고 예상 한대로 수행되고 더 효율적입니다.
- 버그 감지를 허용하는 i 변수에 예기치 않은 일이 발생하면 '영원히 반복'됩니다.
<의 장점
- 다른 말은 단순한 유형의 다른 말만큼 효율적입니다.
- i가 루프에서 증가하거나 5가 루프가 실행되는 동안 수정되는 일부 표현식으로 대체되면 '영원히'실행되지 않습니다.
- float 유형으로 작동합니다.
- 더 읽기 쉬운-익숙해지는 문제
내 결론 :
아마도 ! = 버전은 i가 이산적이고 비교의 다른 쪽이 루프 내에서 변경되지 않는 경우 대부분의 경우에 사용해야합니다.
< 의 존재는 i가 단순 유형 (또는 단순 유형으로 평가됨) 이고 조건이 간단하지 않다는 명확한 신호 이지만, i 또는 조건은 루프 및 / 또는 병렬 처리 내에서 추가로 수정됩니다.
역사적 으로 작은 루프에 대해 선행 증가 연산자 가 접미사보다 선호되는 이유를 아무도 언급하지 않은 것 같습니다 .++i
i++
접두사 (증가 및 가져 오기)와 접미사 (가져 오기 및 증가)의 일반적인 구현을 고려하십시오.
// prefix form: increment and fetch
UPInt& UPInt::operator++()
{
*this += 1; // increment
return *this; // fetch
}
// posfix form: fetch and increment
const UPInt UPInt::operator++(int)
{
const UPInt oldValue = *this;
++(*this);
return oldValue;
}
접두사 작업은 in-place에서 수행 할 수 있습니다 . 접미사는 이전 값을 추적하기 위해 다른 변수가 필요하기 때문입니다. 그 이유가 확실하지 않은 경우 다음을 고려하십시오.
int a = 0;
int b = a++; // b = 0, the old value, a = 1
작은 루프에서 접미사에 필요한 이러한 추가 할당은 이론적으로 속도를 느리게 만들 수 있으므로 이전 학교 논리가 접두사 인 것이 더 효율적입니다. 따라서 많은 C / C ++ 프로그래머는 접두사 형식을 사용하는 습관을 고수했습니다.
However, noted elsewhere is the fact that modern compilers are smart. They notice that when using the postfix form in a for loop, the return value of the postfix is not needed. As such, it's not necessary to keep track of the old value and it can be optimized out - leaving the same machine code you would get from using the prefix form.
Well... that's fine as long as you don't modify i
inside your for loop. The real "BEST" syntax for this entirely depends on your desired result.
If your index were not an int
, but instead (say) a C++ class, then it would be possible for the second example to be more efficient.
However, as written, your belief that the second form is more efficient is simply incorrect. Any decent compiler will have excellent codegen idioms for a simple for loop, and will produce high-quality code for either example. More to the point:
In a for loop that's doing heavy performance-critical computation, the index arithmetic will be a nearly negligible portion of the overall load.
If your for loop is performance-critical and not doing heavy computation such that the index arithmetic actually matters, you should almost certainly be restructuring your code to do more work in each pass of the loop.
When I first started programming in C, I used the ++i
form in for loops simply because the C compiler I was using at the time did not do much optimization and would generate slightly more efficient code in that case.
Now I use the ++i
form because it reads as "increment i", whereas i++
reads as "i is incremented" and any English teacher will tell you to avoid the passive voice.
The bottom line is do whatever seems more readable to you.
I think in the end it boils down to personal preference.
I like the idea of
for(int i = 0; i < 5; i++)
over
for(int i = 0; i != 5; ++i)
due to there being a chance of the value of i jumping past 5 for some reason. I know most times the chances on that happening are slim, but I think in the end its good practice.
We can use one more trick for this.
for (i = 5; i > 0; i--)
I suppose most of the compilers optimize the loops like this. I am not sure. Someone please verify.
Ultimately, the deciding factor as to what is more efficient is neither the language nor the compiler, but rather, the underlying hardware. If you’re writing code for an embedded microcontroller like an 8051, counting up vs. counting down, greater or less than vs. not equals, and incrementing vs. decrementing, can make a difference to performance, within the very limited time scale of your loops.
While sufficient language and compiler support can (and often do) mitigate the absence of the instructions required to implement the specified code in an optimal but conceptually equivalent way, coding for the hardware itself guarantees performance, rather than merely hoping adequate optimizations exist at compile time.
And all this means, there is no one universal answer to your question, since there are so many different low-end microcontrollers out there.
Of much greater importance, however, than optimizing how your for loop iterates, loops, and breaks, is modifying what it does on each iteration. If causing the for loop one extra instruction saves two or more instructions within each iteration, do it! You will get a net gain of one or more cycles! For truly optimal code, you have to weigh the consequences of fully optimizing how the for loop iterates over what happens on each iteration.
All that being said, a good rule of thumb is, if you would find it a challenge to memorize all the assembly instructions for your particular target hardware, the optimal assembly instructions for all variations of a “for” loop have probably been fully accounted for. You can always check if you REALLY care.
I see plenty of answers using the specific code that was posted, and integer. However the question was specific to 'for loops', not the specific one mentioned in the original post.
I prefer to use the prefix increment/decrement operator because it is pretty much guaranteed to be as fast as the postfix operator, but has the possibility to be faster when used with non-primitive types. For types like integers it will never matter with any modern compiler, but if you get in the habit of using the prefix operator, then in the cases where it will provide a speed boost, you'll benefit from it.
I recently ran a static analysis tool on a large project (probably around 1-2 million lines of code), and it found around 80 cases where a postfix was being used in a case where a prefix would provide a speed benefit. In most of these cases the benefit was small because the size of the container or number of loops would usually be small, but in other cases it could potentially iterate over 500+ items.
Depending on the type of object being incremented/decremented, when a postfix occurs a copy can also occur. I would be curious to find out how many compilers will spot the case when a postfix is being used when its value isn't referenced, and thus the copy could not be used. Would it generate code in that case for a prefix instead? Even the static analysis tool mentioned that some of those 80 cases it had found might be optimized out anyway, but why take the chance and let the compiler decide? I don't find the prefix operator to be at all confusing when used alone, it only becomes a burden to read when it starts getting used, inline, as part of a logic statement:
int i = 5;
i = ++i * 3;
Having to think about operator precedence shouldn't be necessary with simple logic.
int i = 5;
i++;
i *= 3;
Sure the code above takes an extra line, but it reads more clearly. But with a for loop the variable being altered is its own statement, so you don't have to worry about whether it's prefix or postfix, just like in the code block above, the i++
is alone, so little thought is required as to what will happen with it, so this code block below is probably just as readable:
int i = 5;
++i;
i *= 3;
As I've said, it doesn't matter all that much, but using the prefix when the variable is not being used otherwise in the same statement is just a good habit in my opinion, because at some point you'll be using it on a non-primitive class and you might save yourself a copy operation.
Just my two cents.
On many architectures, it is far easier to check whether something is zero that whether it is some other arbitrary integer, therefore if you truly want to optimize the heck out of something, whenever possible count down, not up (here's an example on ARM chips).
In general, it really depends on how you think about numbers and counting. I'm doing lots of DSP and mathematics, so counting from 0 to N-1 is more natural to me, you may be different in this respect.
FORTRAN's DO loop and BASIC's FOR loop implemented <
(actually <=
) for positive increments. Not sure what COBOL did, but I suspect it was similar. So this approach was "natural" to the designers and users of "new" languages like C.
Additionally, <
is more likely than !=
to terminate in erroneous situations, and is equally valid for integer and floating point values.
The first point above is the probable reason the style got started, the second is the main reason it continues.
I remember one code segment where the i was getting incremented by 2 instead of 1 due to some mistake and it was causing it to go in infinite loop. So it is better to have this loop as shown in the first option. This is more readable also. Because i != 5 and i < 5 conveys two different meaning to the reader. Also if you are increasing the loop variable then i<5 is suppose to end some point of time while i != 5 may never end because of some mistake.
It is not good approach to use as != 5. But
for (int i =0; i<index; ++i)
is more efficient than
for(int i=0; i<index; i++)
Because i++ first perform copy operation. For detailed information you can look operator overloading in C++.
ReferenceURL : https://stackoverflow.com/questions/1783822/technical-reasons-behind-formatting-when-incrementing-by-1-in-a-for-loop
'programing' 카테고리의 다른 글
더 나은 점 : DataSet 또는 DataReader? (0) | 2021.01.16 |
---|---|
검색 버튼을 표시하거나 키보드에서 버튼을 입력하도록 edittext를 설정하는 방법은 무엇입니까? (0) | 2021.01.16 |
Java 정수를 16 진 정수로 변환 (0) | 2021.01.16 |
모호성에 대한 경고를 제거하는 방법은 무엇입니까? (0) | 2021.01.15 |
iframe src 요청에 요청 헤더를 추가 할 수 있습니까? (0) | 2021.01.15 |