최적화는 주어진 조건이나 범위 내에서 최대의 효율을 발휘하게 만드는 것을 의미한다.
누가? -> 컴파일러가
어떻게? -> 쓸데없는 동작을 빼면서
무엇을? -> 작성한 코드에서
쉽게말해서 컴파일러라는 도구가 작성된 코드를 분석하여 쓸모 없거나 비효율적인 동작을 파악하고 효율적인 코드로 탈바꿈 시켜주는 것을 의미한다.
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;
}
모든 의존성은 변수를 기준으로 나누어져있으며, 상호간 의존성을 기준으로 하나의 블럭으로 묶는다.
여러 블럭들이 컴파일러가 최적화시 최적화 각각의 대상이 되는 것이다.
예를 들어 a = b
와 같은 구문에서 a와 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번의 반복문을 돌면서 파라미터 a로 받은 값을 10으로 바꾸며 바뀐 a의 값을 반환한다.
따라서 결과는 10이 출력된다. 그러면 low level에서는 어떤일이 일어나는지 확인해보자.
아래는 컴파일 최적화 과정을 거치지 않은 low level의 코드다.
실제로 fuc 함수가 call 되면서 해당 함수에 정의된 내용인 for loop를 순회하며 a 변수에 10을 넣는다.
그렇다면 컴파일러 최적화 과정을 거친다면 low level에서는 무슨 일이 일어날까?
위와 같이 fuc 함수는 실제로 call조차 되지 않으며 a 변수에 10이라는 값 역시 들어가지 않는다.
내부적으로는 그냥 10이라는 상수를 printf로 출력해버린다.
이와 같이 컴파일러 최적화 과정은 연산자에 의해서 의존성이 생긴 변수들을 따로 모아 최적화를 진행하며 이때 최적화 과정에서 쓸데없는 연산, 메모리 사용을 획기적으로 줄여준다.
이때 만약 사용자의 무분별한 변수 사용으로 쓸데없는 의존성이 생기고 이로인해 프로그램의 논리적 구조가 복잡해짐에 따라서 컴파일러 최적화를 사용자가 의도한 방향이 아닌 다른 방향으로 시행한다면 프로그램이 Down되는 문제가 생길 수 있다.
효율적인 프로그램 작성을 위해서는 특정 변수에 대해 의존성이 존재하는 연산을 구분해낼 수 있어야하며, 데이터간 의존성을 최대한 낮추는 즉 변수사용을 줄이는 방법으로 프로그램 코드를 작성해야한다.
따라서 꼭 필요한 변수가 아니라면 최대한 상수를 사용하며 하드코딩이 아닌 const를 이용한 코딩을 지향해야 한다.
프로그래머는 최적화 된 코드를 작성하는 것도 중요하지만 다르게 생각해보면 컴파일러가 최적화 하기 쉬운 코드를 작성하는 것이 더 중요하다. 이때 최적화 하기 쉬운 코드라고 하면 의존성이 존재하는 자료를 최대한 줄여 해당 프로그램의 논리적 구조를 최대한 단순하게 제작하여 컴파일러에게 전달하는 방법이다. 즉 최적화를 방해하는 주된 요소는 변수가 많은 것, 포인터의 무분별한 사용이 있으며 따라서 변수는 const를 이용하여 의존성을 낮추며 포인터 변수 역시 const를 사용하여 상수형태로 사용하는 것이 효율적으로 프로그램을 작성하는 것이다.