ATMEGA 처리 속도 테스트 변수 VS BIT단위 with 컴파일러 옵션

ATMEGA에서 변수의 값을 사용해야 할 때, 개별 변수로 사용하는 것과 비트 단위로 사용할 때 걸리는 시간과, 컴파일러 최적화 옵션에 따른 용량/실행 시간 차이를 살펴 보았다

AVR과 같이 메모리가 넉넉하지만은 않은 MCU를 사용하기 위해, 변수의 비트단위까지 짜내는 일이 종종 발생 한다. 물론, 메모리가 넉넉하다면야 필요할 때 마다 독립된 변수를 선언해서 사용하는 것이 코드를 작성하기에도 편하겠지만, 그렇게 마구 남발하다 보면 메모리 제한에 걸려 필요한 변수를 선언할 수 없는 상황에 마주치게 되기도 한다.

메모리 걱정 하지 않고 변수를 펑펑 선언해 가며 사용하는 것과, BIT단위로 쪼개서 메모리를 아껴가며 사용하는 것. 과연 실제 동작에는 어떤 차이가 있을까?

8개의 1bit 데이터를 사용해야 한다 가정하고, 8개의 변수를 선언해서 사용할 때와, 1개의 8bit 변수를 선언한 후, 비트시프트 연산을 해 가며 해당 비트의 값을 읽을 때의 코드를 작성하여, 두 방식의 실행에 걸리는 시간을 테스트 해 본다.

코드 작성

int main(void)
{
  DDRA=DDRB=0xFF;
	unsigned char a=0, b=0, c=0, d=0, e=0, f=0, g=0, h=0;
	a=b=c=d=e=f=g=h=rand();
	PORTA=0XFF;

	if (a==1)	{	PORTB=0X00;	}
	if (b==1)	{	PORTB=0X00;	}	
	if (c==1)	{	PORTB=0X00;	}	
	if (d==1)	{	PORTB=0X00;	}		
	if (e==1)	{	PORTB=0X00;	}	
	if (f==1)	{	PORTB=0X00;	}				
	if (g==1)	{	PORTB=0X00;	}
	if (h==1)	{	PORTB=0X00;	}
		
	if (a==1)	{	PORTB=0X00;	}	
	if (b==1)	{	PORTB=0X00;	}
	if (c==1)	{	PORTB=0X00;	}
	if (d==1)	{	PORTB=0X00;	}
	if (e==1)	{	PORTB=0X00;	}
	if (f==1)	{	PORTB=0X00;	}
	if (g==1)	{	PORTB=0X00;	}
	if (h==1)	{	PORTB=0X00;	}

	if (a==1)	{	PORTB=0X00;	}
	if (b==1)	{	PORTB=0X00;	}
	if (c==1)	{	PORTB=0X00;	}
	if (d==1)	{	PORTB=0X00;	}
	if (e==1)	{	PORTB=0X00;	}
	if (f==1)	{	PORTB=0X00;	}
	if (g==1)	{	PORTB=0X00;	}
	if (h==1)	{	PORTB=0X00;	}

	if (a & (1<<0))	{	PORTB=0X00;	}
	if (a & (1<<1))	{	PORTB=0X00;	}		
	if (a & (1<<2))	{	PORTB=0X00;	}
	if (a & (1<<3))	{	PORTB=0X00;	}
	if (a & (1<<4))	{	PORTB=0X00;	}
	if (a & (1<<5))	{	PORTB=0X00;	}
	if (a & (1<<6))	{	PORTB=0X00;	}
	if (a & (1<<7))	{	PORTB=0X00;	}												

	if (a & (1<<0))	{	PORTB=0X00;	}
	if (a & (1<<1))	{	PORTB=0X00;	}
	if (a & (1<<2))	{	PORTB=0X00;	}
	if (a & (1<<3))	{	PORTB=0X00;	}
	if (a & (1<<4))	{	PORTB=0X00;	}
	if (a & (1<<5))	{	PORTB=0X00;	}
	if (a & (1<<6))	{	PORTB=0X00;	}
	if (a & (1<<7))	{	PORTB=0X00;	}

	if (a & (1<<0))	{	PORTB=0X00;	}
	if (a & (1<<1))	{	PORTB=0X00;	}
	if (a & (1<<2))	{	PORTB=0X00;	}
	if (a & (1<<3))	{	PORTB=0X00;	}
	if (a & (1<<4))	{	PORTB=0X00;	}
	if (a & (1<<5))	{	PORTB=0X00;	}
	if (a & (1<<6))	{	PORTB=0X00;	}
	if (a & (1<<7))	{	PORTB=0X00;	}

	PORTA=0x00;			
}

그리고, 비교의 기준이 있어야 하니, 그냥 PORTA를 켜고 끄는 시간도 측정해 본다.

컴파일러 옵션과 변수-비트 사용에 따른 코드 실행 시간과 크기 결과

  그냥 바로 8변수 (64bit) 1변수 8bit
최적화옵션 O1 O0 O1 O0 O1 O0
코드크기 654B 746B 706B 1082B 834B 1250B
10회 평균 실행 시간 68.22 339.6 271.8 8480 6378 18245
편차 0.344 0.6 0.32 0 3.2 5
각각의 실행 코드 크기와 실행 시간 측정

변수-비트 사용에 따른 어셈블리 코드 라인과 소요 클럭

8bit를 사용해 비트단위 연산을 할 경우 생성되는 어셈블리 코드

if (a & (1<<0))	{	PORTB=0X00;	}
 11e:	8c 01       	movw	r16, r24
 120:	01 70       	andi	r16, 0x01	; 1
 122:	11 27       	eor	r17, r17
 124:	80 fd       	sbrc	r24, 0
 126:	15 b8       	out	0x05, r1	; 5
if (a & (1<<1))	{	PORTB=0X00;	}

8 변수를 사용해 비교 연산을 할 경우 생성되는 어셈블리 코드

if (a==1)	{	PORTB=0X00;	}
 11e:	81 30       	cpi	r24, 0x01	; 1
 120:	c1 f4       	brne	.+48     	; 0x152 <main+0x3e>
 122:	15 b8       	out	0x05, r1	; 5
if (b==1)	{	PORTB=0X00;	}
  명령줄 총 클럭
1변수 8bit 5 5
8변수 64bit 3 2.5
어셈블리 코드의 라인 수와 실행에 걸리는 클럭 수

결론

  1. 메모리 여유가 있으면, 비트연산 하지 말고 그냥 변수 선언해서 하는게 훨씬 빠르다.
  2. 컴파일러 최적화 옵션은 사용하는게 여러모로 좋다.
  3. 이 글은 2020. 11. 30. 13:12 작성 되었습니다.