[C코드 최적화] 집합원소 저장의 효율적 방법

2 minute read

개요

해당 내용은 ‘임베디드 프로그래밍 C코드 최적화’ 책의 story 13에 내용을 정리하였습니다.

데이터 양이 많은 배열이나 구조체를 어떻게 활용하는게 효과적인지 확인할 수있습니다.

배열 엑세스

아래에 작성하는 방법은 최적화 관점에서 작성한 내용입니다. 과도한 최적화는 코드가 지저분해질 수 있으니 참고하여 적절히 활용해주는게 좋습니다.

루프를 이용한 배열 액세스

for (i=0; i<10; i++){
  a[i] = i + 1;
}

루프를 이용한 배열 접근은 프로그래머에게 가장 익숙한 방법입니다. 하지만 효과적인 방법은 아닙니다.

루프변수 i의 접근, 반복조건 확인 및 비교, 변수의 증가 가 반복적으로 수행되어 오버헤드가 발생하기 때문입니다.

하지만 가독성면에서는 좋기 때문에 많이 사용하는 방법입니다.

인덱스 증감을 이용한 배열

i = 0;
a[i++] = k * PI;
a[i++] = 1 * PI;
a[i] = 3 * PI;

배열 인덱스를 증감시켜 배열에 접근하는 방법은 속도면에서 아주 효과적입니다.

루프를 이용했을때와 같은 오버헤드가 발생하지 않고, 주소의 오프셋만 계산하면 되기 때문에 비용을 훨씬 줄일 수 있습니다.

이와 같은 방법은 각 요소에 저장할 값들이 연관성이 없고 서로 다른 값으로 초기화해야 할 경우에 유용합니다.

하드코드 배열 액세스

a[0] = k * PI;
a[1] = 1 * PI;
a[2] = 3 * PI;

하드 코드 배열 접근 방법은 가장 효율적입니다. 각 요소의 주소가 컴파일시에 계산되기 때문입니다. (이정도는 컴파일러가 최적화해줄것 같기도 한데….?)

배열 인덱스의 활용

  • 분기 제거 전
    switch (choose) {
    case 1:
      text = 'a';
      break;
    case 2:
      text = 'b';
      break;
    case 3:
     text = 'c';
     break;
    }
    
  • 분기 제거 후
    static char *buf = "abc";
    text buf[choose];
    

위의 코드는 같은 동작을 하는 코드입니다. 하지만 분기제거를 하게 되면 바로 배열의 인덱스로 활용하기 때문에 분기나 비교가 일어나지 않습니다.

이러한 방법은 조건에 따른 함수 호출시에도 활용이 가능합니다.

  • 분기 제거 전 ```c switch (choose) { case 1: fl(); break; case 2: f2(); break; case 3: f3(); break; }

  • 분기 제거 후

    void (*p[3]) () = (fl, f2, 3
    p[choose]();
    

p는 함수포인터 배열이며 함수포인터로 초기화되었습니다. f1, f2, f3은 함수 시작주소를 가르키는 포인터입니다. choose의 값에 따라 함수 포인터가 선택되고, 선택된 함수 포인터를 통해 함수가 호출되게 됩니다.

구조체의 패딩 비트를 줄여라

#include <stdio.h>

struct Test1 {
  char a;
  int b;
  char c;
  int d;
};

struct Test2 {
  char a;
  char c;
  int b;
  int d;
};

void main() {
  printf("Test1 size : %d\n", sizeof(struct Test1));
  printf("Test2 size : %d\n", sizeof(struct Test2));
}

위의 코드를 보면 Test1과 Test2 구조체가 있습니다. 순서는 조금 다르지만 char형 변수 2개와 int형 변수 2개가 있습니다. 이 두 구조체의 사이즈는 어떻게 될까요?

실행 결과는 다음과 같습니다.

$ ./struct_size_test
Test1 size : 16
Test2 size : 12

맴버의 크기대로라면 두 구조체 모두 10이여야 합니다. 하지만 실제 할당받은 메모리 크기는 그것보다 조금 크게 나타납니다. 그 이유는 중간에 패딩 비트가 들어갔기 대문입니다. 메모리에 데이터가 저장될 때 하드웨어적으로 접근할 수 있는 주소와 그렇지 않은 주소가 있는데, 이를 워드정렬이라고 합니다. 워드정렬되지 않은 데이터가 저장될 수 없기 때문에 패팅이 이뤄지게 됩니다.

예를 들어 32비트의 마이크로프로세서는 4의배수로만 주소 접근이 가능합니다. 그렇기때문에 컴파일시 워드 정렬을 하여 구조체가 형성되게 됩니다.

Test1 과 Test2 의 구조체의 데이터가 저장되는 형태는 다음과 같다.

image

Leave a comment