Notice
Recent Posts
Recent Comments
Link
«   2026/10   »
일 월 화 수 목 금 토
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

헤맨 만큼 내 땅이다

발표 자료 — CS:APP 3.8.4~3.8.5 (249~253쪽) 정리 본문

카테고리 없음

발표 자료 — CS:APP 3.8.4~3.8.5 (249~253쪽) 정리

신속다람 2026. 9. 18. 16:13

도입 (30초)

이번 발표는 **"컴파일러가 다차원 배열 인덱싱을 어떻게 최적화하는가"**에 대한 내용입니다. 핵심 질문은 하나예요 — 배열 원소에 접근할 때마다 매번 곱셈을 해야 할까? 결론부터 말하면, 컴파일러는 최대한 이 곱셈을 피하려고 합니다.


1. 고정크기 배열 (3.8.4절) — 2분

예제 설정

#define N 16
typedef int fix_matrix[N][N];

int fix_prod_ele(fix_matrix A, fix_matrix B, long i, long k) {
    long j;
    int result = 0;
    for (j = 0; j < N; j++)
        result += A[i][j] * B[j][k];
    return result;
}

→ 두 16×16 행렬을 곱했을 때, 결과의 (i,k) 위치 값을 구하는 함수입니다. 행렬 곱셈의 정의 그대로, A의 i행과 B의 k열을 내적합니다.

배열 주소 공식 (식 3.1)

&D[i][j] = xD + L(C·i + j)      # L=원소크기, C=열 개수

컴파일러의 최적화

원본 코드는 매번 j로 인덱싱하며 곱셈을 반복하지만, gcc는 이걸 **포인터 3개(Aptr, Bptr, Bend)**로 바꿔버립니다.

int *Aptr = &A[i][0];
int *Bptr = &B[0][k];
int *Bend = &B[N][k];
do {
    result += *Aptr * *Bptr;
    Aptr++;
    Bptr += N;
} while (Bptr != Bend);

왜 빨라지는가 — 핵심

  • N=16이 컴파일 시점에 이미 알려진 상수이기 때문에, 주소 = xA + 64i 같은 계산에서 ×64를 **시프트 연산(salq $6)**으로 바꿀 수 있습니다.
  • 루프 안에서는 곱셈 없이 덧셈만 반복합니다 (Aptr++, Bptr += 64).
  • 정리: **"반복되는 곱셈 → 사전 계산 한 번 + 반복 덧셈"**으로 바꾸는 것 = 이번 절의 핵심 최적화 기법.

+ 응용 예제 (연습문제 3.40, 대각선 채우기)

for (i = 0; i < N; i++) A[i][i] = val;

대각선은 행과 열이 동시에 1씩 증가하므로, 포인터가 "N+1"칸(=68바이트)씩 건너뛰면 됩니다. 같은 원리의 변형일 뿐입니다.


2. 가변크기 배열 (3.8.5절) — 1.5분

배경: C99부터는 배열 크기를 변수로 선언할 수 있게 됐습니다.

int var_ele(long n, int A[n][n], long i, long j) {
    return A[i][j];
}

(주의: 크기 n이 반드시 배열보다 먼저 선언되어야 함 — 컴파일러가 차원을 미리 알아야 하므로)

고정크기 버전과의 결정적 차이

고정크기 (N=16) 가변크기 (n)

n·i 계산 salq $6 (시프트) imulq (진짜 곱셈)

→ n이 실행 중에야 정해지는 값이라, 컴파일러가 "몇 비트 시프트할지"를 미리 알 수 없습니다. 그래서 곱셈을 피할 수 없습니다 — **"이 경우에는 어쩔 수 없다"**고 책도 명시합니다.

루프 최적화도 여전히 일부 적용됨

int *Arow = A[i];
int *Bptr = &B[0][k];
for (j = 0; j < n; j++) {
    result += Arow[j] * *Bptr;
    Bptr += n;
}

B는 포인터로 바뀌지만, A는 인덱스(Arow[j])를 그대로 유지 — 책은 이게 "필연적 차이가 아니라 컴파일러의 선택에 따른 결과일 뿐"이라고 설명합니다.


3. 결론 — 이 두 절을 관통하는 핵심 원리 (1분)

배열 인덱싱은 본질적으로 곱셈(식 3.1)이다. 하지만 같은 배열을 규칙적으로 반복 접근할 때는, 그 곱셈을 매번 새로 계산하지 않고 "이전 위치 + 고정 간격"이라는 덧셈으로 대체할 수 있다.

  • 고정크기: 크기를 컴파일 시점에 아니까 → 곱셈 자체(×L, ×C)도 시프트로 바꿀 수 있어 가장 빠름
  • 가변크기: 크기를 실행 시점에만 아니까 → 곱셈은 피할 수 없지만, 루프 안 반복 곱셈은 덧셈으로 대체 가능

결국 "배열 크기를 컴파일러가 얼마나 미리 알고 있느냐"가 최적화 수준을 결정한다는 것이 두 절의 핵심 메시지입니다.


발표용으로 슬라이드(PPT)나 문서 파일로 변환해드릴까요, 아니면 이 정리 그대로 참고하시면서 말로 발표하실 건가요?