요약

  • 대형 페이지(Huge Page) 기능을 활용하여 TLB 미스로 인한 오버헤드를 최소화할 수 있습니다.
  • 워킹셋 크기가 크고(수십 MB 이상), 메모리 접근이 매우 빈번한 워크로드에서 유용한 최적화 기법입니다.
    • 주로 데이터베이스 시스템과 관련된 레퍼런스(Oracle, Postgres, AWS RDS)가 많습니다.
    • 대형 JVM 애플리케이션
    • 하이퍼바이저(KVM/QEMU)
    • 대규모 분석·검색 시스템(예: ClickHouse)
  • CPU 및 I/O 바운드가 지배적인 워크로드에서는 별다른 효용이 없을 수 있습니다.

배경

  • 모든 프로세스는 가상 메모리 주소 공간을 할당받습니다.
  • 프로세스에서 가상 메모리 주소에 접근하면 CPU는 이를 실제 물리 메모리 주소로 치환하여 처리합니다.
  • OS는 가상 메모리 주소를 물리 메모리 주소로 매핑하기 위한 Page Table을 관리합니다.

  • 프로세스가 메모리에 접근할 때마다, 가상 메모리 주소를 물리 메모리 주소로 변환하는 과정이 매번 발생합니다.
  • CPU는 이 변환 과정을 최적화하기 위해 최근 변환된 메모리 주소를 캐싱하는데, 이 캐시를 TLB(Translation Lookaside Buffer)라고 부릅니다.

  • 문제는 TLB의 크기가 일반적으로 매우 작다는 점입니다(~4KB).
  • 따라서 워킹셋 크기가 큰 경우에는 TLB 미스가 빈번하게 발생합니다.
  • TLB의 크기는 제한적이므로, 대신 페이지 크기를 늘려 TLB 적중률을 높이는 방법이 고안되었습니다.

Huge Pages

  • 리눅스에서는 페이지 크기를 늘릴 수 있는 두 가지 옵션을 제공합니다.
    • hugetlbfs
      • 리눅스에서 대형 페이지(Huge Page)를 활용하기 위해 제공되는 특별한 파일시스템입니다.
        • 커널이 제공하는 가상 파일시스템으로, /dev/hugepages와 같은 경로를 마운트할 수 있습니다.
        • 프로세스가 이 파일시스템에 파일을 만들고 mmap()을 하면, 그 매핑은 대형 페이지 단위로 할당됩니다.
        • 즉, 일반적인 malloc이 아니라 hugetlbfs로 마운트한 경로를 통해 대형 페이지 메모리를 사용합니다.
      • 특징
        • 시스템 메모리 중에서 hugetlbfs가 사용할 대형 페이지 개수를 미리 설정해야 합니다.
          • 예약된 대형 페이지는 다른 용도로 사용되지 않습니다.
          • 애플리케이션이 이를 사용하지 않으면 그대로 낭비됩니다.
        • 애플리케이션 코드에서 메모리를 사용하는 방식을 바꿔야 합니다.
          • C처럼 메모리를 명시적으로 할당하는 언어로 작성된 프로그램은 코드를 수정해야 합니다.
          • JVM과 같은 런타임에서는 -XX:+UseHugeTLBFS 옵션을 사용하면 됩니다.
      • 활용 레퍼런스
        • 초단타 매매(HFT), 데이터베이스(Oracle, Postgres, AWS RDS) 및 캐시 서버 등에서 활용됩니다.
    • Transparent Huge Pages (THP)

검증

  • 아래는 벤치마크 테스트에 활용된 코드 예시입니다.
    • 임의 크기의 byte[]를 생성하고 무작위 인덱스에 접근합니다.
    • 바이트 배열의 크기가 커질수록 많은 페이지가 할당되며 TLB 미스가 발생할 가능성이 높아집니다.
public class ByteArrayTouch {

    @Param(...)
    int size;

    byte[] mem;

    @Setup
    public void setup() {
        mem = new byte[size];
    }

    @Benchmark
    public byte test() {
        return mem[ThreadLocalRandom.current().nextInt(size)];
    }
}
  • 벤치마크 테스트 결과, 바이트 배열의 크기가 큰 구간에서 최대 15% 수준의 성능 개선이 이루어졌습니다.
Benchmark               (size)  Mode  Cnt   Score   Error  Units

# Baseline
ByteArrayTouch.test       1000  avgt   15   8.109 ± 0.018  ns/op
ByteArrayTouch.test      10000  avgt   15   8.086 ± 0.045  ns/op
ByteArrayTouch.test    1000000  avgt   15   9.831 ± 0.139  ns/op
ByteArrayTouch.test   10000000  avgt   15  19.734 ± 0.379  ns/op
ByteArrayTouch.test  100000000  avgt   15  32.538 ± 0.662  ns/op

# -XX:+UseTransparentHugePages
ByteArrayTouch.test       1000  avgt   15   8.104 ± 0.012  ns/op
ByteArrayTouch.test      10000  avgt   15   8.060 ± 0.005  ns/op
ByteArrayTouch.test    1000000  avgt   15   9.193 ± 0.086  ns/op // !
ByteArrayTouch.test   10000000  avgt   15  17.282 ± 0.405  ns/op // !!
ByteArrayTouch.test  100000000  avgt   15  28.698 ± 0.120  ns/op // !!!

# -XX:+UseHugeTLBFS
ByteArrayTouch.test       1000  avgt   15   8.104 ± 0.015  ns/op
ByteArrayTouch.test      10000  avgt   15   8.062 ± 0.011  ns/op
ByteArrayTouch.test    1000000  avgt   15   9.303 ± 0.133  ns/op // !
ByteArrayTouch.test   10000000  avgt   15  17.357 ± 0.217  ns/op // !!
ByteArrayTouch.test  100000000  avgt   15  28.697 ± 0.291  ns/op // !!!
  • CPU 카운터를 분석한 결과는 다음과 같습니다.
    • 일반 페이지를 사용한 경우에는 100%에 가까운 TLB 미스가 발생했습니다.
    • 반면, THP를 사용한 경우 TLB 미스가 거의 발생하지 않았습니다.
Benchmark                                (size)  Mode  Cnt    Score    Error  Units

# Baseline
ByteArrayTouch.test                   100000000  avgt   15   33.575 ±  2.161  ns/op
ByteArrayTouch.test:cycles            100000000  avgt    3  123.207 ± 73.725   #/op
ByteArrayTouch.test:dTLB-load-misses  100000000  avgt    3    1.017 ±  0.244   #/op  // !!!
ByteArrayTouch.test:dTLB-loads        100000000  avgt    3   17.388 ±  1.195   #/op

# -XX:+UseTransparentHugePages
ByteArrayTouch.test                   100000000  avgt   15   28.730 ±  0.124  ns/op
ByteArrayTouch.test:cycles            100000000  avgt    3  105.249 ±  6.232   #/op
ByteArrayTouch.test:dTLB-load-misses  100000000  avgt    3   ≈ 10⁻³            #/op  // !!!
ByteArrayTouch.test:dTLB-loads        100000000  avgt    3   17.488 ±  1.278   #/op

레퍼런스