E D R , A S I H C RSS

i++VS++i

C/C++ 에서 ++i 와 i++ 의 성능 차이에 대한 비교

  • 사용한 컴파일러 : Microsoft 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86 (Microsoft Visual C++ 6.0 에 Service Pack 5 를 설치했을때의 컴파일러)




1. 그냥 사용

  • 비교한 C/C++ 소스

~cpp 
#include <stdio.h>
void main()
{
	int i;
	scanf("%d", &i);
	++i;
	printf("%d", i);
}
~cpp 
#include <stdio.h>
void main()
{
	int i;
	scanf("%d", &i);
	i++;    // 이렇게 하면 차이가 당연히 없지 않을까요? 이럴때는 선행이든 후행이든 증가한뒤에 printf에서 그 변수를 사용했으니..
	printf("%d", i);     // 문제가 되는건 함수(i++)또는 함수(++i)이런데에서 문제가 생길거 같은데..
}
간단히 생각하면 되는 것이었습니다.. i++ 이나 ++i 모두 값(value)을 생성하는 연산자 입니다. 그러므로.. i 가 5 일때 i++ 의 값은 5이므로.. printf("%d", i) 는 5를 찍어주겠지요.

1.1. 최적화하지 않을때

  • ++i 의 컴파일된 어셈블리 소스
    ~cpp 
    	mov	ecx, DWORD PTR _i$[ebp]	; 변수 i 인 _i$[ebp] 를 ecx 로 옮기고
    	add	ecx, 1			; ecx 에 1 을 더하고
    	mov	DWORD PTR _i$[ebp], ecx	; ecx 를 다시 _i$[ebp] 로
    

  • i++ 의 컴파일된 어셈블리 소스
    ~cpp 
    	mov	ecx, DWORD PTR _i$[ebp]	; 차이 없음
    	add	ecx, 1
    	mov	DWORD PTR _i$[ebp], ecx
    

1.2. 크기 최적화

  • ++i 의 컴파일된 어셈블리 소스
    ~cpp 
    	inc	DWORD PTR _i$[ebp]		; 변수 i 인 _i$[ebp] 를 1 만큼 증가시킴
    

  • i++ 의 컴파일된 어셈블리 소스
    ~cpp 
    	inc	DWORD PTR _i$[ebp]		; 차이 없음
    

1.3. 속도 최적화

  • ++i 의 컴파일된 어셈블리 소스
    ~cpp 
    	mov	eax, DWORD PTR _i$[esp+12]	; 변수 i 인 _i$[esp+12] 를 eax 로 옮기고
    	inc	eax			; eax 를 1 만큼 증가시킴
    

  • i++ 의 컴파일된 어셈블리 소스
    ~cpp 
    	mov	eax, DWORD PTR _i$[esp+12]	; 차이 없음
    	inc	eax
    


2. for 문에서 사용

  • 비교한 C/C++ 소스

~cpp 
#include <stdio.h>
void main()
{
	for(int i = 0 ; i < 10 ; ++i)
		printf("%d", i);
}
~cpp 
#include <stdio.h>
void main()
{
	for(int i = 0 ; i < 10 ; i++)
		printf("%d", i);
}

2.1. 최적화하지 않을때

  • ++i 의 컴파일된 어셈블리 소스
    ~cpp 
    	mov	eax, DWORD PTR _i$[ebp]	; 변수 i 인 _i$[ebp] 를 eax 로 옮기고
    	add	eax, 1			; eax 에 1 을 더하고
    	mov	DWORD PTR _i$[ebp], eax	; eax 를 다시 _i$[ebp] 로
    

  • i++ 의 컴파일된 어셈블리 소스
    ~cpp 
    	mov	eax, DWORD PTR _i$[ebp]	; 차이 없음
    	add	eax, 1
    	mov	DWORD PTR _i$[ebp], eax
    

2.2. 크기 최적화

  • ++i 의 컴파일된 어셈블리 소스
    ~cpp 
    	inc	esi			; 변수 i 인 esi 를 1 만큼 증가시킴
    

  • i++ 의 컴파일된 어셈블리 소스
    ~cpp 
    	inc	esi			; 차이 없음
    

2.3. 속도 최적화

  • ++i 의 컴파일된 어셈블리 소스
    ~cpp 
    	inc	esi			; 변수 i 인 esi 를 1 만큼 증가시킴
    

  • i++ 의 컴파일된 어셈블리 소스
    ~cpp 
    	inc	esi			; 차이 없음
    


3. 함수 에서 사용

  • 비교한 C/C++ 소스

~cpp 
#include <stdio.h>
void main()
{
	int i;
	scanf("%d", &i);
	printf("%d", ++i);
}
~cpp 
#include <stdio.h>
void main()
{
	int i;
	scanf("%d", &i);
	printf("%d", i++);
}

3.1. No 최적화

  • ++i 의 컴파일된 어셈블리 소스
    ~cpp 
    	mov	ecx, DWORD PTR _i$[ebp]
    	add	ecx, 1
    	mov	DWORD PTR _i$[ebp], ecx
    	mov	edx, DWORD PTR _i$[ebp]
    	push	edx
    	push	OFFSET FLAT:$SG528
    	call	_printf
    

  • i++ 의 컴파일된 어셈블리 소스
    ~cpp 
    	mov	ecx, DWORD PTR _i$[ebp]
    	mov	DWORD PTR -8+[ebp], ecx
    	mov	edx, DWORD PTR -8+[ebp]
    	push	edx
    	push	OFFSET FLAT:$SG528
    	mov	eax, DWORD PTR _i$[ebp]
    	add	eax, 1
    	mov	DWORD PTR _i$[ebp], eax
    	call	_printf
    

3.2. 크기 최적화

  • ++i 의 컴파일된 어셈블리 소스
    ~cpp 
    	inc	DWORD PTR _i$[ebp]
    	push	DWORD PTR _i$[ebp]
    	push	esi
    	call	_printf
    

  • i++ 의 컴파일된 어셈블리 소스
    ~cpp 
    	mov	eax, DWORD PTR _i$[ebp]
    	inc	DWORD PTR _i$[ebp]
    	push	eax
    	push	esi
    	call	_printf
    

3.3. 속도 최적화

  • ++i 의 컴파일된 어셈블리 소스
    ~cpp 
    	mov	eax, DWORD PTR _i$[esp+12]
    	inc	eax
    	push	eax
    	push	OFFSET FLAT:??_C@_02MECO@?$CFd?$AA@
    	mov	DWORD PTR _i$[esp+20], eax
    	call	_printf
    

  • i++ 의 컴파일된 어셈블리 소스
    ~cpp 
    	mov	eax, DWORD PTR _i$[esp+12]
    	mov	ecx, eax
    	inc	eax
    	push	ecx
    	push	OFFSET FLAT:??_C@_02MECO@?$CFd?$AA@
    	mov	DWORD PTR _i$[esp+20], eax
    	call	_printf
    

4. 연산자 재정의로 구현했을때

class 에서 operator overloading 으로 전위증가(감소)와 후위증가(감소)는 다음과 같이 구현되어 있다.
~cpp 
MyInteger& MyInteger::operator++() // 전위증가
{
 *this += 1;
 return *this;
}
const MyInteger MyInteger::operator++(int) // 후위증가. 전달인자로 int 가 있지만
                                         // 컴파일러는 내부적으로 operator++(0)을 호출한다.
{
 const MyInteger oldValue = *this;
 ++(*this);

 return oldValue;
}
 
연산자 재정의를 하여 특정 개체에 대해 전위증가와 후위증가를 사용할 때에는 전위증가가 후위증가보다 효율이 좋다. operator++(int) 함수에서는 임시 객체를 생성하는 부분이 있다.
- from MoreEffectiveC++

4.1. STL 에서

++i, 나 i++ 둘다 상관 없는 상황이라면, ++i에 습관을 들이자, 위의 연산자 재정의는 STL을 사용한다면 일반적인 경우이다. 후위 연산자가 구현된 Iterator는 모두 객체를 복사하는 과정을 거친다. 컴파일러단에서 Iterator 의 복사를 최적화 할수 있는 가능성에서는 보장할 수 없다. 따라서, 다음과 같은 경우
~cpp 
static const int MAX = 5;
int array[5] = {1,2,3,4,5}
vector<int> intArray(&array[0], &array[MAX]);

for(vector<int>::iterator i = intArray.begin(); i != intArray.end(); ++i){
    cout << *i << endl;
}
가 객체 복사를 하지 않는다. 객체가 크다면 이 비용은 무시할 수 없다. 더 궁금하면, STL 소스를 분석해 보자.
--NeoCoin

5. 결론

그냥 사용한 경우나, for 문에서 사용한 경우는 ++i 와 i++ 의 성능 차이가 없다. 그러나 함수의 전달인자로 사용한 경우는 ++i 보다 i++ 의 코드가 명령어 한개 정도 길어진다. 하지만 그냥 사용한 경우나 for 문에서 사용한 경우에는 i++ 을 쓴 곳을 ++i 로 서로 바꿔 써도 상관 없으나, 함수의 전달인자로 사용한 경우에는 i++ 을 쓴 곳을 ++i 로 바꾸면 실행 결과가 달라진다. 그러므로 함수에서 i++ 을 사용하고 있을 경우 프로그램이 한 줄 이라도 추가되지 않고 ++i 로 바꿀수 있으면 바꾸는 것이 더 효율적이다. 또한 그냥 사용할 경우나, for 문에서 사용한 경우는 ++i 를 쓰지 않아도 상관 없다. --상규

6. 토론

  • 아악... 어셈이다. 전 봐도 모르겠으니 걍 제가 본 글귀를 그대로 인용하겠습니다.

~cpp 
여기에서 교과서적인 이야기를 하나 하고 넘어가야 할 것 같다. 루프 안에서 항상 선행 증가를 사용하는 것이 좋은 이유는 무엇일까?

효율성 때문이라는 것이 정답이다. 후행 증가 연산자는 변수의 이전 값을 돌려 주므로 이전 값을 담을 임시적인 변수를 만들고 파괴하는 과정이 일어나게 된다. 
후행 증가로도 선행 증가와 동일한 방식의 루프를 만드는 것이 가능하지만, 후행 증가를 사용할 특별한 이유가 없다면 항상 선행 증가 또는 선행 감소 연산자를 
사용하는 것이 바람직히다.
요즘 컴파일러들은 최적화가 잘 되어서 이전 값을 돌려주기위해 이전 값을 담을 임시 변수를 만들고 값을 증가시킨 후 임시 변수에 있는 이전 값을 돌려주고 임시 변수를 파괴하는 방식으로 하지 않고, 이전 값을 먼저 돌려주고 값을 증가시킵니다.

속도가 아주 빨라야 하는 프로그래밍을 할 때는 특정 컴파일러에서 어떻게 할 때가 성능이 더 좋은지 알 필요가 있을수도 있다.
물론 특정 컴파일러라는 것이 언제나 명시되어야 한다. 일반화는 일반적으로 옳지 않다.

쩝.. 저는 별로 신경쓰지 않는데요... (무감각하다는;;;) Amdahl's Law 였나.. 프로그램 속도를 증가시키려면, 제일 시간을 많이 잡아먹는 부분을 수정하라고... 쩝.. 마이크로 프로그램이나. 리얼타임 어플리케이션같은곳에서는 필요할수도 있겠군요.; - 임인택

7. ++i 찬성

동일한 기능에 쓴다면, ++i 에 습관을 들이는 것을 추천한다. STL같은 연산자 재정의 라이브러리 때문 --NeoCoin

8. i++ 찬성

i++은 그 특유의 기능이 필요할 때만 쓰는것이 좋을것 같다. i++를 쓰면 다음과 같이 두줄이 한줄로 주는 경우가 있기 때문이다.

현재 자료를 찍고, 다음 자료로 카운팅을 하려 한다.

~cpp 
...
cout << data[i] << endl
i += 1;
...
가 이렇게 한줄로 주는 후위 연산자의 기능을 이용할때 쓰자.

~cpp 
cout << data[i++] << endl;
Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2021-02-07 05:31:42
Processing time 0.0430 sec