본문으로 건너뛰기

안동민 개발노트

본문 시작

TLB와 페이지 테이블 최적화

TLB 적중률이 주소 변환 비용에 미치는 영향을 계산하고 ASID와 다단계 페이지 테이블의 공간 절약 원리를 이해합니다.

페이지 테이블은 메모리에 저장됩니다.

그런데 메모리에 접근하려면 먼저 페이지 테이블을 조회해야 하고, 페이지 테이블 자체도 메모리에 있으니 메모리 접근이 한 번 더 필요합니다.

데이터 하나를 읽으려면 메모리를 두 번 접근해야 합니다 — 페이지 테이블 참조 + 실제 데이터.

성능이 절반으로 떨어집니다.

이 문제를 해결하는 것이 TLB입니다.

TLB와 페이지 테이블 최적화

페이지 테이블은 메모리에 저장됩니다. 그런데 메모리에 접근하려면 먼저 페이지 테이블을 조회해야 하고, 페이지 테이블 자체도 메모리에 있으니 메모리 접근이 한 번 더 필요합니다.

  1. 주소 변환 캐시를 타는 순서

    TLB(Translation Lookaside Buffer)는 최근 사용된 페이지-프레임 매핑을 저장하는 고속 하드웨어 캐시입니다. 2 프로세스마다 페이지 테이블이 다릅니다. 3 문제: 페이지 테이블 크기 32비트 시스템에서 4KB 페이지를 사용하면 페이지 테이블 엔트리가 $2^{20} \approx 100$만 개입니다. 4 일반 페이지 테이블은 논리 주소 → 물리 주소 방향입니다.

  2. TLB의 역할

    TLB(Translation Lookaside Buffer)는 최근 사용된 페이지-프레임 매핑을 저장하는 고속 하드웨어 캐시입니다.

  3. 컨텍스트 스위칭과 TLB

    프로세스마다 페이지 테이블이 다릅니다.

  4. 다단계 페이지 테이블

    문제: 페이지 테이블 크기 32비트 시스템에서 4KB 페이지를 사용하면 페이지 테이블 엔트리가 $2^{20} \approx 100$만 개입니다.

  5. 역 페이지 테이블

    일반 페이지 테이블은 논리 주소 → 물리 주소 방향입니다.

  6. TLB miss와 테이블 크기 압박 확인

    데이터 하나를 읽으려면 메모리를 두 번 접근해야 합니다 — 페이지 테이블 참조 + 실제 데이터. 이 문제를 해결하는 것이 TLB입니다. TLB는 최근 사용된 페이지-프레임 매핑을 저장하는 고속 캐시입니다.


TLB의 역할

TLB와 페이지 테이블 최적화에서는 주소 변환, 테이블 갱신, 메모리/디스크 비용을 확인합니다.

TLB hit 여부가 주소 변환 비용을 가른다

hit는 캐시된 변환을 바로 쓰지만 miss는 page table walk와 TLB 갱신이 필요해 메모리 접근 횟수가 늘어난다.

  1. VPN 조회

    lookup VPN 조회 TLB tag 비교

  2. PFN 반환

    hit PFN 반환 메모리 접근 바로 진행

  3. page walk

    miss page walk PTE 단계 탐색

  4. TLB 갱신

    fill TLB 갱신 다음 접근을 빠르게

TLB(Translation Lookaside Buffer)는 최근 사용된 페이지-프레임 매핑을 저장하는 고속 하드웨어 캐시입니다.

CPU 칩 안에 위치하며, 연관 메모리(Associative Memory)로 구현되어 모든 엔트리를 동시에(병렬로) 비교할 수 있습니다.

동작 과정

  1. CPU가 논리 주소를 생성합니다.
  2. 페이지 번호로 TLB를 병렬 검색합니다 (하드웨어가 모든 엔트리를 동시 비교).
  3. TLB 히트(Hit): 프레임 번호를 즉시 얻어 물리 주소를 생성합니다. 추가 메모리 접근 없음.
  4. TLB 미스(Miss): 메모리의 페이지 테이블을 참조하여 프레임 번호를 얻고, 결과를 TLB에 저장(캐싱)합니다.

유효 접근 시간 (EAT) 계산

메모리 접근 시간을 tt, TLB 검색 시간을 ϵ\epsilon (보통 무시 가능), TLB 적중률을 α\alpha라고 하면:

EAT=α×(t+ϵ)+(1α)×(2t+ϵ)EAT = \alpha \times (t + \epsilon) + (1 - \alpha) \times (2t + \epsilon)

ϵ\epsilon을 무시하면:

EAT=α×t+(1α)×2t=t×(2α)EAT = \alpha \times t + (1 - \alpha) \times 2t = t \times (2 - \alpha)

적중률 α\alphaEAT (t=100ns 가정)성능 저하
100%100ns0%
99%101ns1%
90%110ns10%
80%120ns20%
0% (TLB 없음)200ns100%

TLB 크기는 보통 64~2048개 엔트리로 작지만, 적중률은 99% 이상을 달성합니다.

프로그램의 지역성(Locality) 덕분입니다.

시간적 지역성: 최근 접근한 주소를 곧 다시 접근합니다 (루프, 함수 재호출).

공간적 지역성: 접근한 주소 근처를 곧 접근합니다 (배열 순차 접근, 인접 코드).

4KB 페이지 × 1024개 TLB 엔트리 = 4MB의 주소 공간을 TLB가 커버합니다.

대부분의 워킹 셋이 이 범위 안에 들어갑니다.

대규모 페이지(2MB)를 사용하면 TLB 커버리지가 2GB로 확대됩니다.

TLB 성능은 단순히 빠른 캐시가 있다는 사실보다, 적중률이 무너지는 조건을 구분하는 것이 중요합니다.

주소 변환 병목은 적중률, 커버리지, 전환 비용으로 읽는다

TLB는 작은 캐시라서 빠르지만, 워킹 셋이 커지거나 주소 공간이 자주 바뀌면 미스 비용이 바로 드러납니다.

  1. 히트율 1% 하락도 누적됩니다

    EAT 미스는 페이지 테이블 walk를 부르고, 다단계 테이블에서는 여러 번의 메모리 참조로 확대됩니다.

  2. 평균 주소 변환 비용

    EAT = 0.98×1 + 0.02×5 = 1.08 hit 98%, miss 시 4단계 walk라면 miss 2%도 비용을 8% 높인다.

  3. miss 원인 구분

    TLB miss↑ · fault↓ → TLB coverage TLB miss↑ · switch↑ → flush/ASID fault↑ · swap↓ → working set fault↑ · swap↑ → memory pressure

  4. hit
    즉시 변환

    hit 페이지 번호가 TLB에 있으면 프레임 번호를 바로 얻습니다.

  5. miss
    테이블 walk

    miss 계층별 PTE를 따라가며 프레임 또는 폴트 원인을 찾습니다.

  6. fill
    엔트리 적재

    fill 찾은 매핑을 TLB에 넣고 다음 접근을 빠르게 만듭니다.

  7. switch
    ASID 판정

    switch 태그가 있으면 유지하고, 없으면 플러시로 워밍업이 필요합니다.

  8. 튜닝 레버와 장애 신호

    엔트리 수 × 페이지 크기. Huge Page는 범위를 키우지만 내부 낭비도 키웁니다. 루프와 배열 순회는 히트를 높이고, 랜덤 접근은 미스를 늘립니다. 짧은 time slice와 많은 프로세스는 TLB 워밍업 비용을 증가시킵니다.

  9. 커버리지

    엔트리 수 × 페이지 크기. Huge Page는 범위를 키우지만 내부 낭비도 키웁니다.

  10. 지역성

    루프와 배열 순회는 히트를 높이고, 랜덤 접근은 미스를 늘립니다.

  11. 전환 빈도

    짧은 time slice와 많은 프로세스는 TLB 워밍업 비용을 증가시킵니다.


컨텍스트 스위칭과 TLB

프로세스마다 페이지 테이블이 다릅니다.

컨텍스트 스위칭 시 TLB 처리 방법은 두 가지입니다.

TLB 플러시 (Flush)

가장 단순한 방법: 컨텍스트 스위칭 시 TLB 전체를 비웁니다.

새 프로세스의 첫 번째 메모리 접근부터 TLB 미스가 발생하여 페이지 테이블을 참조합니다.

TLB가 워밍업 되기까지 성능이 떨어집니다.

이것이 컨텍스트 스위칭의 숨겨진 비용입니다.

레지스터 저장/복원은 수십 나노초이지만, TLB 워밍업에 수천~수만 나노초가 소요될 수 있습니다.

ASID (Address Space Identifier)

TLB 엔트리에 프로세스 ID(ASID)를 태그합니다.

TLB에 여러 프로세스의 매핑이 동시에 존재해도, ASID로 구분하므로 충돌이 없습니다.

컨텍스트 스위칭 시 TLB를 비울 필요가 없어 성능이 크게 향상됩니다.

x86에서는 PCID(Process Context IDentifier)라는 이름으로 12비트(4096개 ID)를 지원합니다.

ARM에서는 8비트(256개) ASID를 지원합니다.

ASID가 있으면 프로세스가 바뀌어도 TLB를 모두 비우지 않는다

TLB entry에 주소 공간 식별자를 함께 저장하면 같은 VPN이라도 어느 프로세스의 변환인지 구분할 수 있다.

  1. process change

    switch process change 주소 공간 전환

  2. flush

    no ASID flush 오래된 변환 제거

  3. tag compare

    with ASID tag compare 같은 ASID만 hit

  4. warm TLB

    reuse warm TLB 전환 후 miss 감소

tlb_simulation.py
class TLB:
    """간단한 TLB 시뮬레이터"""
    def __init__(self, size=64):
        self.size = size
        self.entries = {}  # {(asid, page): frame}
        self.access_order = []
        self.hits = 0
        self.misses = 0

    def lookup(self, asid, page_num):
        key = (asid, page_num)
        if key in self.entries:
            self.hits += 1
            self.access_order.remove(key)
            self.access_order.append(key)
            return self.entries[key]
        self.misses += 1
        return None

    def insert(self, asid, page_num, frame_num):
        key = (asid, page_num)
        if len(self.entries) >= self.size:
            # LRU 교체
            evict = self.access_order.pop(0)
            del self.entries[evict]
        self.entries[key] = frame_num
        self.access_order.append(key)

    def hit_ratio(self):
        total = self.hits + self.misses
        return self.hits / total if total > 0 else 0

# 시뮬레이션
tlb = TLB(size=4)
accesses = [(1,0), (1,1), (1,2), (1,0), (1,1), (2,0), (1,0)]
for asid, page in accesses:
    if tlb.lookup(asid, page) is None:
        tlb.insert(asid, page, page * 10)  # 가상 프레임
print(f"TLB 적중률: {tlb.hit_ratio():.1%}")

다단계 페이지 테이블

문제: 페이지 테이블 크기

32비트 시스템에서 4KB 페이지를 사용하면 페이지 테이블 엔트리가 2201002^{20} \approx 100만 개입니다.

엔트리당 4바이트라면 페이지 테이블 하나가 4MB를 차지합니다.

프로세스가 100개이면 페이지 테이블만으로 400MB가 필요합니다.

대부분의 프로세스는 주소 공간의 극히 일부만 사용합니다.

Text 세그먼트(낮은 주소)와 Stack 세그먼트(높은 주소) 사이의 거대한 중간 영역은 비어 있습니다.

사용하지 않는 영역의 100만 개 엔트리까지 메모리에 올려두는 것은 낭비입니다.

해결: 계층적 분할

다단계 페이지 테이블(Multi-level Page Table)은 "페이지 테이블 자체를 페이지 단위로 나누고, 사용하는 부분만 메모리에 올리는" 아이디어입니다.

2단계 페이지 테이블 (32비트 x86):

비트용도
상위 10비트1단계 인덱스(Page Directory)
중간 10비트2단계 인덱스(Page Table)
하위 12비트페이지 오프셋

1단계 테이블(Page Directory)은 1024개 엔트리로 항상 메모리에 있습니다(4KB).

각 엔트리가 2단계 테이블을 가리킵니다.

2단계 테이블은 사용되는 것만 메모리에 올립니다.

주소 공간의 1%만 사용하는 프로세스는 2단계 테이블 10여 개(약 40KB)만 존재합니다.

4MB 대비 100배 절약입니다.

64비트의 4단계 페이지 테이블

64비트 x86-64는 실제로 48비트 가상 주소를 사용하며, 4단계 페이지 테이블을 거칩니다.

단계x86-64 이름인덱스 비트
1단계PML4 (Page Map Level 4)비트 47-39 (9비트)
2단계PDPT (Page Directory Pointer)비트 38-30 (9비트)
3단계PD (Page Directory)비트 29-21 (9비트)
4단계PT (Page Table)비트 20-12 (9비트)
오프셋-비트 11-0 (12비트)

4단계를 거치면 메모리 접근이 4번 추가로 필요합니다.

TLB 히트가 99%라면 대부분의 경우 이 오버헤드를 피할 수 있지만, TLB 미스 시 페널티가 큽니다.

대규모 페이지(2MB)를 사용하면 3단계, 1GB 페이지는 2단계만 거치므로 미스 페널티가 줄어듭니다.

Linux 6.0+는 5단계 페이지 테이블(PML5)을 지원하여 57비트 가상 주소(128PB)를 사용할 수 있습니다.

TLB가 빗나가면 다단계 페이지 테이블 walk가 실제 비용으로 드러납니다.

아래 다이어그램은 히트 경로와 미스 경로의 차이를 비교합니다.

히트는 한 번, 미스는 페이지 테이블 walk

TLB가 매핑을 갖고 있으면 바로 프레임 번호를 얻지만, 미스가 나면 다단계 테이블을 따라가며 여러 번의 메모리 접근이 추가됩니다.

  1. 1
    TLB가 페이지 → 프레임을 즉시 반환

    Hit path 추가 접근 0 CPU page # TLB 병렬 검색 Frame # 오프셋과 결합해 물리 주소 완성

  2. 2
    x86-64 4단계 페이지 테이블을 순서대로 확인

    Miss path 최대 4단계 PML4 상위 인덱스로 다음 테이블 위치 확인 PDPT 주소 공간의 큰 구간을 좁힘 PD 2MB 대규모 페이지면 여기서 종료 가능 PT 4KB 페이지의 최종 프레임 번호 획득

  3. 3
    정밀하지만 단계가 많음

    4KB page 정밀하지만 단계가 많음 작은 페이지라 내부 단편화는 적지만 TLB 미스 시 마지막 PT까지 갑니다.

  4. 4
    PT를 건너뛸 수 있음

    2MB page PT를 건너뛸 수 있음 큰 연속 영역은 PD 엔트리에서 바로 프레임 범위를 얻어 미스 비용을 줄입니다.

  5. 5
    더 큰 커버리지

    1GB page 더 큰 커버리지 TLB 한 엔트리가 더 넓은 주소 범위를 담당해 대용량 워킹 셋에 유리합니다.


역 페이지 테이블

일반 페이지 테이블은 논리 주소 → 물리 주소 방향입니다.

프로세스 수만큼 테이블이 존재합니다.

역 페이지 테이블(Inverted Page Table)은 반대입니다.

물리 프레임별로 어떤 프로세스의 어떤 페이지가 들어 있는지를 기록합니다.

시스템 전체에 테이블이 하나만 있습니다.

테이블 크기가 물리 메모리의 프레임 수에 비례하므로, 프로세스 수에 관계없이 일정합니다.

물리 메모리가 4GB이고 4KB 페이지이면, 엔트리 수는 2201002^{20} \approx 100만 개로 고정됩니다.

단점: 주소 변환 시 (프로세스 ID, 페이지 번호) 쌍으로 전체 테이블을 탐색해야 합니다.

O(N)O(N) 탐색은 느리므로, 해시 테이블과 함께 구현하여 O(1)O(1) 룩업을 달성합니다.

IBM PowerPC, UltraSPARC 등이 역 페이지 테이블을 사용했습니다.

다음 절에서는 페이지가 물리 메모리에 없을 때 발생하는 페이지 폴트요구 페이징을 다루겠습니다.

TLB와 페이지 테이블 비용 기준

가상 주소 변환은 TLB hit이면 빠르지만 miss와 컨텍스트 스위칭이 겹치면 페이지 테이블 탐색 비용이 드러납니다.

  1. 01

    VA 입력

  2. 02

    TLB 조회

  3. 03

    PT walk

  4. 04

    PA 생성

  5. 05

    캐시 접근

  6. VA page 42

    TLB lookup

  7. TLB hit

    frame 즉시 획득 data 1회 접근

  8. 4-level PT walk

    PML4 → PDPT → PD → PT → data 최대 5회 접근

  9. TLB Hit 최근 사용한 VPN-PFN 매핑

    바로 찾으면 메모리 접근 전 주소 변환 비용이 작습니다.

  10. TLB Flush/ASID 프로세스 전환 시 flush

    줄이려면 주소 공간 식별자로 매핑을 구분합니다.

  11. Multi-level PT 사용하지 않

    가상 주소 범위의 하위 테이블을 만들지 않아 메모리를 아낍니다.

  12. Inverted PT 물리 프레임 중심

    매핑을 저장해 큰 주소 공간에서 테이블 크기를 줄입니다.

TLB와 주소 변환

페이지 테이블을 매 메모리 접근마다 조회하면 주소 변환이 병목이 됩니다. TLB는 최근 사용한 page to frame 매핑을 CPU 가까이에 저장해 변환 비용을 크게 낮추는 작은 캐시입니다.

  1. 요청

    ASID 7 · VPN 42 process 경계까지 키에 포함

  2. TLB entry 비교

    (7,42) → PFN 9 · R/W ASID 또는 VPN이 다르면 miss

  3. 물리 주소 생성

    PFN 9 + offset miss면 PT walk 후 entry fill

  4. TLB 우선 확인

    CPU가 만든 page number를 TLB에서 찾고 hit이면 frame number를 즉시 얻습니다. hit

  5. miss 처리

    TLB에 없으면 페이지 테이블을 조회하고 유효한 매핑이면 TLB에 채웁니다. miss

  6. 권한도 함께 검사한다

    TLB entry에는 frame뿐 아니라 valid, dirty, protection 같은 정보가 함께 들어갈 수 있습니다. permission

  7. 프로세스 경계를 지킨다

    다른 프로세스의 같은 page number와 섞이지 않도록 ASID를 쓰거나 context switch 때 비웁니다. ASID