ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 컴파일 최적화
    C언어 2020. 3. 27. 20:34

    최적화란 ?

    최적화 쉽게 말해 가장 알맞는 상태로 맞춘다는 의미이다. 그렇다면 누가 어떻게 무엇을 알맞는 상태로 맞춘다는 것인가

    누가 ? 컴파일러가

    어떻게 ? 쓸데없는 동작은 빼면서

    무엇을 ? 코드에서

    쉽게말해 컴파일러라는 도구가 작성된 코드를 분석하여 쓸모 없거나 비효율적인 동작을 파악하고 효율적은 코드로 탈바꿈 시켜주는 것을 이야기 한다. 아래 예를 한번 살펴보자

    int main(int argc, char *argv[]){
    	// 변수 b는 변수 a에 대해 의존적이다.
    	int a = 10;
    	int b = a * 5;
    
    	// 변수 d는 변수 c에 대해 의존적이다.
    	int c = 20;
    	int d = c * 5;
    
    	// 변수a와 변수b는 변수c와 변수d와 서로 의존적이지 않다.
    	// 즉 순서를 바꾸거나 동시에 연산을 진행해도 문제가 발생하지 않는다 즉 동시성을 가진다.
    	
    
    return 0;
    
    
    }

     위의 코드를 볼때 의존성의 기준이 무엇인가 생각해 보자 

    모든 의존성은 변수를 기준으로 나누었으며 이렇게 나눈 상호간의 의존성을 기준으로 상호 의존성이 존재할 경우 하나의 블럭으로 묶는다. 이렇게 묶은 여러 블럭들이 컴파일러가 최적화시 최적화 각각의 대상이 된다.

    C에서 프로그래밍은 코드의 절차적 흐름에 따라 즉 연산자에 따라 동작하게 되며 이때 중요하게 생각해야 할 부분이 이 연산자에 의해 의존성이 생기는 Data(변수) 들을 꼭 구분해야 할 필요가 있다.

    예를들어 " a = b " 이와 같은 구문이 있을 때 이는 '=' 연산자에 의해 data a 는 data b에 의존성이 생기게 된다. 이와 같은 개념을 확실히 구분하고 컴파일러 최적화 이슈에 대비해야 한다. 아래 예시를 보자    

    int fuc(int a){
    	for (int i = 0; i < 10; ++i){
    		a = 10;
    	}
    	return a;
    }
    
    
    
    int main(int argc, char *argv[]){
    	int a = 10;
    	printf("%d", fuc(a));
    
    return 0;
    
    
    }
    

     fuc 함수를 정의하였으며 해당 함수는 10번의 Loop 를 돌면서 파라미터로 받은 a의 값을 10 으로 바꾸며 바뀐 a의 값을 반환한다. 따라서 결과는 10이 출력될 것이다. 그렇다면 Low Level 에서 어떤 일이 일어나는지 살펴보자

     실제로 fuc 함수가 Call 되며 해당 함수에 정의된 내용인 for Loop 를 순회하며 a 변수에 10을 넣는다.

     

    그렇다면 컴파일러 최적화 과정을 거친다면 Low Level 에서 코드는 어떻게 변화 될까? 

    위와 같이 fuc 함수는 실제로 Call 조차 되지 않으며 a 변수에 10이라는 값 역시 들어가지 않는다. 내부적으로는 그냥 10 이라는 상수를 printf 함수로 출력해 버린다.

    이와같이 컴파일러 최적화 과정은 연산자에 의해 의존성이 생긴 변수들을 따로 모아서 최적화를 진행하며 이때 최적화 과정에서 쓸데 없는 연산, 메모리 사용을 획기적으로 줄여준다.

    그렇다면 이때 만약 사용자의 무분별한 변수 사용으로 인해 쓸데없는 의존성이 생기고 이로인해 프로그램의 논리적 구조가 복잡해 짐에 따라 컴파일러가 최적화를 사용자가 의도한 방향이 아닌 다른 방향으로 시행한다면 프로그램이 Down 되는 문제가 생길 것이다.

    따라서 효율적인 프로그램을 작성하기 위해서는 작성자는 특정 변수(Data)에 대해 의존성이 존재하는 연산을 구분해낼 수 있어야 하며 Data 간의 의존성을 최대한 낮추는 즉 변수사용을 줄이는 방법으로 프로그램 코드를 작성해야 한다.

    따라서 꼭 필요한 변수가 아니라면 최대한 상수를 사용하며 이때 상수는 하드코딩이 아닌 const 를 이용한 코딩을 해야한다.

    + 포인터 역시 본질은 변수이기 때문에 C++에서는 이러한 본질적 문제를 최대한 보완하기 위해 & (참조자)를 사용하며 참조자의 역할은 포인터 변수를 상수화 시킨 즉 int* const ptr = &a 이와 같은 동작을 수행하게 된다.

     

    * 요약 *

    프로그래머는 최적화 된 코드를 작성하는 것도 중요하지만 다르게 생각해보면 컴파일러가 최적화 하기 쉬운 코드를 작성하는 것이 더 중요하다. 이때 최적화 하기 쉬운 코드라고 하면 의존성이 존재하는 자료를 최대한 줄여 해당 프로그램의 논리적 구조를 최대한 단순하게 제작하여 컴파일러에게 전달하는 방법이다. 즉 최적화를 방해하는 주된 요소는 변수가 많은 것, 포인터의 무분별한 사용이 있으며 따라서 변수는 const를 이용하여 의존성을 낮추며 포인터 변수 역시 const를 사용하여 상수형태로 사용하는 것이 효율적으로 프로그램을 작성하는 것이다. 

     

    'C언어' 카테고리의 다른 글

    컴파일 전 처리 (전처리기)  (0) 2020.03.28
    메모리 구조  (0) 2020.03.16
    C 프로그램의 동작 과정  (0) 2020.03.16
    스택 프레임 & 재귀함수  (0) 2020.03.13
    포인터의 이해  (0) 2020.03.07

    댓글

Designed by Tistory.