여기서 논하는 내용들은 의외로 꼼수라고 느껴질정도로 별거 아닌 기법이지만 그런 플로우들이 많이 사용 될 때 아낄 수 있는 cost는 무시하지 못한다고 생각한다. 우리는 그런 최적화를 유발하는 일이 뭐가 있는지부터 알아본다.
우리가 아무리 멋진 최적화 기법을 알고 있어도, 컴파일러가 왜 코드를 마음대로 최적화하지 못하는지 이해하는 것이 중요하다. 보통은 두 가지 원인이 있다.
- 메모리 앨리어싱 (memory aliasing)
- 함수 호출 (function calls)
메모리 앨리어싱 | Memory Aliasing
메모리 앨리어싱은 서로 다른 이름(포인터)이 사실은 동일한 메모리 위치를 가리키는 상황을 말한다.
앨리어스(alias)가 '별명'이나 '가명'이라는 뜻인 걸 생각하면 이름에서 유추하기가 괜찮다.
컴파일러는 코드를 분석할 때, 두 포인터가 서로 다른 메모리를 가리키는지, 아니면 같은 곳의 '별명'인지 확신 할 수 없다. 그래서 혹시 모를 상황에 대비해 매우 보수적으로 접근하게 된다.
예를 들어, *p1에 어떤 값을 쓰는 코드가 있다고 해보자.
만약 다른 포인터 *p2가 p1과 같은 주소를 가리키고 있다면, *p1의 변경은 *p2의 값에도 영향을 줄 것이다.
컴파일러는 이 가능성을 절대 무시할 수 없기 때문에,
메모리 읽기/쓰기 순서를 마음대로 바꾸거나 불필요해 보이는 연산을 생략하는 최적화를 섣불리 시도 할 수 없다.
그렇다면 컴파일러가 이런 보수적인 태도를 취해야만 하는 상황에서,
아래 코드의 두 줄은 서로 실행 순서를 바꿀 수 있을까?
// x, y는 int* 타입의 포인터라고 가정합니다.
*x = *x + *y; // 1번 라인
*y = *x + *y; // 2번 라인
- 앨리어싱이 없는 경우 (x와 y가 다름):x = 1, y = 2 라고 가정해보면..
- x = *x + *y; -> x는 1 + 2 = 3이 된다.
- y = *x + *y; -> y는 3 + 2 = 5가 된다. 최종: x = 3, y = 5
- 앨리어싱이 발생한 경우 (x와 y가 같음):x와 y가 같은 메모리를 가리키고, 그 값이 1이라고 가정해보자.
- x = *x + *y; -> x는 1 + 1 = 2가 되었다. (y도 2가 되었다.)
- y = *x + *y; -> y는 2 + 2 = 4가 되었다. (x도 4가 되었다.) 최종: x = 4, y = 4
함수 호출 | Function Calls
컴파일러에게 함수는 일종의 '블랙박스'와 같다.
함수를 호출하면 프로그램의 제어권이 잠시 그 함수로 넘어가는데,
컴파일러는 그 함수 안에서 무슨 일이 일어날지 예측하기 어렵다.
예를 들어 이런 일이 일어난다:
- 함수가 전역 변수의 값을 바꿀 수도 있다.
- (Side effect 라고 부르는) 우리가 전달한 포인터가 가리키는 메모리의 내용을 수정할 수도 있다.
이런 불확실성 때문에 컴파일러는 함수 호출 전후의 코드 순서를 마음대로 바꾸거나,
레지스터에 저장해 둔 값을 함수 호출 후에도 그대로 사용할 수 있다고 가정 할 수 없다.
그래서 함수 호출이 있으면 최적화에 매우 소극적으로 변한다.
그렇다면 아래와 같은 코드에서,
컴파일러는 왜 strlen(s)를 루프 시작 전에 한 번만 계산하는 최적화를 섣불리 적용하기 어려울까?
for (int i = 0; i < strlen(s); i++) {
lower_case(s);
}
컴파일러 입장에서는 함수가 '블랙박스'라고 했다.
컴파일러는 이 함수가 구체적으로 무슨 일을 하는지 들여다보지 않는다. 그냥 '그런 함수가 있다' 고만 인지할 뿐이다.
void lower_case(char *s) {
// ... 다른 문자들을 소문자로 바꾸는 코드 ...
// 그런데 만약 문자열의 중간을 잘라버리는 코드가 있다면?
if (some_condition) {
s[5] = '\0'; // 문자열의 5번째 인덱스 뒤를 잘라버린다.
}
}
만약 이런 코드가 lower_case 함수 내부에 숨어있다면, 루프가 돌 때마다 s의 길이가 바뀔 여지가 생긴 것이다.
strlen(s)의 결과값이 계속 달라지는 것이다.
컴파일러는 lower_case 함수가 이런 ‘흉악한 짓’을 하지 않을 것이라고 확신 할 수 없다.
따라서 안전을 위해 매번 루프의 조건을 확인할 때마다 strlen(s)을 다시 호출해서 길이를 새로 계산하는, 비효율적이지만 안전한 방법을 택한다.
'컴퓨터 이론 > CS:APP' 카테고리의 다른 글
Input Cash to get Cache [CSAPP Chap 6] (4) | 2025.09.01 |
---|---|
컴퓨터를 덜 느리게 하는 방패 [CSAPP Chap 5] (2) | 2025.09.01 |
나노미터 단위로 배관공사하기 [CSAPP Chap 4] (2) | 2025.08.31 |
Young한 Y86-64로 구조 알아보기 [CSAPP Chap 4] (2) | 2025.08.31 |
제어 흐름도 논하는 어셈블리 명렁어 [CSAPP Chap 3] (3) | 2025.08.27 |