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