Brise

Efficient C coding 본문

프로그램/C,C++

Efficient C coding

naudhizb 2014. 9. 30. 17:22
반응형

Efficient C-coding 에서는 다음과 같은 목표들을 이루기 위해 코딩을 수행한다.


- 낮은 연산시간

- 적은 코드 사이즈

- 적은 램 차지용량

- 적은 레지스터 사용량


컴파일 시간보다는, 컴파일된 코드의 실행시간이 중요하다.

gcc의 최적화 옵션을 사용하면, 실행시간이 줄어들 수 있지만,

사용자가 원하지 않은 결과를 낼 가능성이 존재하기 때문에

(매뉴얼의 방법에 맞춰서 컴파일한다면 그런 가능성이 줄어든다.)

가능하다면, 최대한 사용자가 컴파일러에 친숙하게 코딩 하는 것이 중요하다.




0. 변수선언

룩업테이블 (메모리 H, 연산시간 L)


1. 연산관련

2. 분기관련

2.1 루프

2.2 if-else / switch

2.3 함수 call


1. 연산 관련


- 부동 소수점 연산을 피하자(Avoid float, double)

- 가능하다면 unsigned 변수형을 사용하다.

- 모듈레이션 연산을 피하자


int Tick(int tick)

{

return ++tick%60;

}

대신에


int Tick(int tick)

{

++tick;

if(tick    >= 60)

return 0;

return tick;

}

- 나눗셈 연산을 피하자


int mb_to_kb    (int mb)

{

return mb*1024;

}


대신에


int mb_to_kb    (int mb)

{

return mb<<10;

}





- 나눗셈보단 곱셈

For example, (a / b) > c can be rewritten as a > (c * b)

- 비트 연산 활용하기




2. 프로그램 플로우 관련


2-1. 루프

- 일반식을 이용하자.

ex) (n+1)*n/2

- 루프의 결과 값을 얻으면 바로 탈출하다.

- 루프 한번에 많은 일을 하는 것이 좋다. 

- 가능하다면 독립적인 일을 하는 두개의 루프를 합치는 것이 좋다.

- 루프 조건문의 비교값은 0과 비교하는 것이 좋다.

거의 대부분의 아키텍처에서는 0에 도달할 때 ZERO 플래그를 재설정한다. 때문에 줄어든 변수를 명시적으로 0과 비교할 필요가 없다. 이런 방법을 응용하면 for 루프 속도를 높이는 한 가지 힌트를 찾을 수 있다.

- 가능하다면 루프를 쓰지 않는 것이 좋다.(Unrolling)


2-2. if 및 switch 문

- if else VS switch?

값이 순차성을 띄는 경우에는 switch가 더 빠르다.

하지만, 값이 띄엄 띄엄 있는 경우에는 if else문이 더 빠르다.

같은 변수에 대해 비교하는 if else문의 길이가 길어진다면 이진 탐색 알고리즘을 사용하여 코딩하자.

- 룩업 테이블을 사용하자. 

char * Condition_String1(int condition) 

        switch(condition) 

        { 

        case 0: return "EQ"; 

        case 1: return "NE"; 

        case 2: return "CS"; 

        case 3: return "CC"; 

        case 4: return "MI"; 

        case 5: return "PL"; 

        case 6: return "VS"; 

        case 7: return "VC"; 

        case 8: return "HI"; 

        case 9: return "LS"; 

        case 10: return "GE"; 

        case 11: return "LT"; 

        case 12: return "GT"; 

        case 13: return "LE"; 

        case 14: return ""; 

        default: return 0; 

        } 

    }


이것 보다는


char * Condition_String2(int condition) 

    if ((unsigned) condition >= 15) 

    { 

        return 0; 

    } 

    return 

        "EQ\0NE\0CS\0CC\0MI\0PL\0VS\0VC\0HI\0LS\0GE\0LT\0GT\0LE\0\0" + 

        3 * condition; 

}

위와 같은 코드를 활용하자.

코드의 길이도 짧아지고, 프로그램의 크기도 작아진다.


<><>

● 분기 제거

루프문에 분기가 들어가면 성능이 떨어진다. 때문에 루프 내에서 분기를 어떻게 제거해야 할 지 고민해야 한다. 만약 최적화 컴파일러라는 것이 있어서 이런 일들을 개발자가 일일이 하지 않는다면 몰라도 일단 아직은 모두 수작업으로 해야 하는 일들이다. <리스트 11>을 살펴보자.

 

<리스트 11> 분기가 있는 루프문

do

{

printf("첫번째 출력\n");

if (--a < 0) break;

printf("두번째 출력\n");

} while (1);

 

코드 논리상으로는 큰 문제가 없어 보인다. 하지만 <리스트 11>을 <리스트 12>와 같이 수정하면 루프문 안에서 조건문을 제거할 수 있어 성능을 향상시킬 수 있다.

 

<리스트 12> <리스트 11>에서 분기가 제거된 코드

printf("첫번째 출력\n");

a--;

if (a>=0) {

do

{

printf("두번째 출력\n");

printf("첫번째 출력\n");

} while (--a<0);

}




2-3. 함수

- 가능한 함수의 사용을 자제하자. (함수를 호출하기 위한 오버헤드가 존재한다.)

- 인라인 함수를 활용하자.

#include <stdio.h> 

int max(int a, int b) 

    if(a > b) 

        return a;

    return b; 

__inline int imax(int a, int b) 

    if(a > b) 

        return a;

    return b; 

int main() 

    printf("4 와 5 중 큰 것은?", max(4,5)); 

    printf("4 와 5 중 큰 것은?", imax(4,5)); 

    return 0; 

}

- call by reference(포인터에 의한 함수 인자값 전달)을 활용하자.

- 자주 참조되는 포인터 변수/전역변수의 경우 지역변수를 할당하고 함수의 끝에 다시 리턴하자.(Using Aliases)

 void func1( int *data )    {

      int i;


     for(i=0; i<10; i++) 

     {

            anyfunc( *data, i);

     }

  }

보다는

void func1( int *data )

  {

      int i;

      int localdata;


      localdata = *data;

      for(i=0; i<10; i++)

      {

          anyfunc ( localdata, i);

      }

  }

가 더 낫다.

- 전달되는 포인터에서 chain을 사용하는 경우 chain의 값을 줄여주자


a->b->c


에서


int* d = a->b;

d->c;

- 함수로 인자전달을 할 때에 인자의 갯수를 4개 이하로 줄여주자.

만약 많다면 구조체를 선언해서 그 구조체에 넣어 전달하자. 스택에 넣는 메모리의 양이 주는만큼 속도가 빨라진다. 넣는 변수의 크기는 long 이상으로 할 것.


> ARM ABI 정의 규칙인 APCS(ARM Procedure Standard Call)에 따르면 컴파일러는 함수로 넘기는 인수를 담는 레지스터네 개를(a1~a4) 할당한다. 즉 표준 C로 함수를 선언할 때 인수를 네 개 이하로 유지할 경우 인수를 넘기기 위해 스택을 사용하지 않고 레지스터만 사용하게 되므로 상당한 속도 개선의 효과를 얻을 수 있다.


- 함수 내부에서 사용되는 지역변수의 숫자를 일정 숫자 이하로 줄이자.

ARM의 경우에는 GP Register가 16개이다. 16개의 지역변수 이상을 사용한다면 퍼포먼스가 16개에 비하여 크게 낮아질 가능성이 높다.


3. 임베디드 시스템


3-1. GPIO

- Memory mapped I/O 이므로, 레지스터의 직접제어를 활용하자.

http://nexp.tistory.com/tag/MCU%EC%86%8D%EB%8F%84%EC%B8%A1%EC%A0%95



한국어 ARM C코드 최적화 설명서

DUI0205IK_rvct_comp_user_guide.pdf



AN3674.pdf

<http://cache.freescale.com/files/dsp/doc/app_note/AN3674.pdf>


ESC_Boston_01_304_paper.pdf

<http://www.open-std.org/Jtc1/sc22/wg21/docs/ESC_Boston_01_304_paper.pdf>


미리 배열 저장해놓기

http://cafe.naver.com/whig/301431


C언어 최적화 개론적인 간단한 모음들

http://itguru.tistory.com/129


구조체 배열 pack

http://euro87.tistory.com/150


씹어먹는 c언어 강좌

http://itguru.tistory.com/187


임베디드 환경 최적화

http://katalog.egloos.com/viewer/2969559


efficient C coding 한글/원문

http://blog.naver.com/zjvmf6429/140027190209

추가 한글 https://kldp.org/node/79109

http://www.codeproject.com/Articles/6154/Writing-Efficient-C-and-C-Code-Optimization

http://www.azillionmonkeys.com/qed/optimize.html


ARM efficient C-coding

http://www.davespace.co.uk/arm/efficient-c-for-arm/


http://books.google.co.kr/books?id=oWKwAwAAQBAJ&pg=PT440&lpg=PT440&dq=arm+c+%EC%B5%9C%EC%A0%81%ED%99%94&source=bl&ots=OGNmB3lc6o&sig=nZfQQX3P952KElbY4_pqmWIRDW0&hl=ko&sa=X&ei=4XEqVOKMC8S78gXUlICoCg&ved=0CEgQ6AEwBQ#v=onepage&q=arm%20c%20%EC%B5%9C%EC%A0%81%ED%99%94&f=false



반응형

'프로그램 > C,C++' 카테고리의 다른 글

2차원 포인터의 값과 주소 접근 방식  (0) 2015.12.12
C언어 표준매크로  (0) 2015.08.20
C언어의 변수형 정리  (0) 2015.08.14
Call by value, Call by reference  (0) 2015.05.11
2차원 배열 함수 호출 예제  (0) 2015.05.11
함수 포인터 배열 예제  (0) 2015.02.13
쉽게 이해하는 포인터  (0) 2014.09.01
Comments