TCMalloc : Thread-Caching Malloc

동기

TCMalloc은 시스템 기본 할당자의 대안으로 설계된 메모리 할당자로, 다음과 같은 특징을 가지고 있습니다:

  • 빠르고 경합 없는 할당 및 해제: 대부분의 객체에 대해 빠른 할당과 해제를 제공합니다. 객체는 모드에 따라 스레드별 또는 논리적 CPU별로 캐싱됩니다. 대부분의 할당은 락을 필요로 하지 않아 경합이 적고 멀티스레드 애플리케이션에서 우수한 확장성을 제공합니다.
  • 메모리의 유연한 사용: 해제된 메모리를 다른 객체 크기에 재사용하거나 OS에 반환할 수 있습니다.
  • 낮은 객체당 메모리 오버헤드: 동일한 크기의 객체 “페이지”를 할당하여 작은 객체의 공간 효율적인 표현을 가능하게 합니다.
  • 낮은 오버헤드 샘플링: 애플리케이션의 메모리 사용에 대한 상세한 인사이트를 제공합니다.

사용법

Bazel의 바이너리 규칙에서 malloc 속성으로 TCMalloc을 지정하여 사용할 수 있습니다.

개요

다음 블록 다이어그램은 TCMalloc의 대략적인 내부 구조를 보여줍니다:

TCMalloc은 프론트엔드(front-end), 미들엔드(middle-end), 백엔드(back-end) 세 가지 구성 요소로 나눌 수 있습니다. 각 부분의 대략적인 역할은 다음과 같습니다:

  • 프론트엔드는 애플리케이션에 빠른 메모리 할당 및 해제를 제공하는 캐시입니다.
  • 미들엔드는 프론트엔드 캐시를 리필하는 역할을 담당합니다.
  • 백엔드는 OS로부터 메모리를 가져오는 작업을 처리합니다.

프론트엔드는 CPU별 또는 레거시 스레드별 모드로 실행할 수 있으며, 백엔드는 hugepage 인식 pageheap 또는 레거시 pageheap을 지원할 수 있습니다.

TCMalloc 프론트엔드

프론트엔드는 특정 크기의 메모리 요청을 처리합니다. 프론트엔드는 할당에 사용하거나 여유 메모리를 보관하는 메모리 캐시를 가지고 있습니다. 이 캐시는 한 번에 하나의 스레드만 접근할 수 있어 락이 필요하지 않으므로 대부분의 할당과 해제가 빠릅니다.

프론트엔드는 적절한 크기의 캐싱된 메모리가 있으면 모든 요청을 만족시킵니다. 특정 크기의 캐시가 비어 있으면 프론트엔드는 미들엔드에서 메모리 배치를 요청하여 캐시를 리필합니다. 미들엔드는 CentralFreeList와 TransferCache로 구성됩니다.

미들엔드가 고갈되었거나 요청된 크기가 프론트엔드가 캐시하는 최대 크기보다 큰 경우, 요청은 백엔드로 가서 큰 할당을 만족시키거나 미들엔드의 캐시를 리필합니다. 백엔드는 PageHeap이라고도 합니다.

TCMalloc 프론트엔드에는 두 가지 구현이 있습니다:

  • 스레드별 캐시: 원래는 객체의 스레드별 캐시를 지원했습니다(그래서 Thread Caching Malloc이라는 이름이 붙었습니다). 그러나 이는 스레드 수에 따라 확장되는 메모리 풋프린트를 초래했습니다. 현대 애플리케이션은 많은 스레드 수를 가질 수 있으며, 이는 많은 양의 스레드별 메모리 집합 또는 많은 스레드가 아주 작은 스레드별 캐시를 갖는 결과를 초래합니다.
  • CPU별 모드: 최근 TCMalloc은 CPU별 모드를 지원합니다. 이 모드에서는 시스템의 각 논리적 CPU가 메모리를 할당하는 자체 캐시를 가집니다. 참고: x86에서 논리적 CPU는 하이퍼스레드와 동등합니다.

스레드별 모드와 CPU별 모드의 차이점은 malloc/new 및 free/delete 구현에만 국한됩니다.

작은 객체와 큰 객체 할당

“작은” 객체의 할당은 60-80개의 할당 가능한 크기 클래스 중 하나에 매핑됩니다. 예를 들어, 12바이트 할당은 16바이트 크기 클래스로 반올림됩니다. 크기 클래스는 다음으로 큰 크기 클래스로 반올림할 때 낭비되는 메모리 양을 최소화하도록 설계되었습니다.

__STDCPP_DEFAULT_NEW_ALIGNMENT__ <= 8로 컴파일하면, ::operator new로 할당된 원시 저장소에 대해 8바이트로 정렬된 크기 세트를 사용합니다. 이 작은 정렬은 16바이트의 배수로 반올림되는 많은 일반적인 할당 크기(24, 40 등)에 대해 낭비되는 메모리를 최소화합니다. 많은 컴파일러에서 이 동작은 -fnew-alignment=... 플래그로 제어됩니다. __STDCPP_DEFAULT_NEW_ALIGNMENT__가 지정되지 않았거나 8바이트보다 큰 경우, ::operator new에 대해 표준 16바이트 정렬을 사용합니다. 그러나 16바이트 미만의 할당의 경우, 더 큰 정렬 요구 사항을 가진 객체가 해당 공간에 할당될 수 없으므로 더 낮은 정렬의 객체를 반환할 수 있습니다.

특정 크기의 객체가 요청되면 해당 요청은 SizeMap::GetSizeClass() 함수를 사용하여 특정 크기 클래스의 요청으로 매핑되고, 반환된 메모리는 해당 크기 클래스에서 나옵니다. 이는 반환된 메모리가 요청된 크기보다 최소한 크다는 것을 의미합니다. 크기 클래스의 할당은 프론트엔드에서 처리됩니다.

kMaxSize로 정의된 제한보다 큰 크기의 객체는 백엔드에서 직접 할당됩니다. 따라서 프론트엔드나 미들엔드에 캐싱되지 않습니다. 큰 객체 크기에 대한 할당 요청은 TCMalloc 페이지 크기로 반올림됩니다.

해제

객체가 해제될 때, 컴파일러는 컴파일 시간에 알려진 경우 객체의 크기를 제공합니다. 크기를 알 수 없는 경우 pagemap에서 조회됩니다. 객체가 작으면 프론트엔드 캐시로 다시 반환됩니다. 객체가 kMaxSize보다 크면 pageheap으로 직접 반환됩니다.

CPU별 모드

CPU별 모드에서는 하나의 큰 메모리 블록이 할당됩니다. 다음 다이어그램은 이 메모리 슬랩이 CPU 간에 어떻게 분할되고 각 CPU가 슬랩의 일부를 사용하여 메타데이터와 사용 가능한 객체에 대한 포인터를 보관하는지 보여줍니다.

각 논리적 CPU에는 메타데이터와 특정 크기 클래스의 사용 가능한 객체에 대한 포인터를 보관하는 이 메모리의 섹션이 할당됩니다. 메타데이터는 크기 클래스당 하나의 헤더 블록으로 구성됩니다. 헤더는 크기 클래스별 객체 포인터 배열의 시작에 대한 포인터와 현재 동적 최대 용량 및 해당 배열 세그먼트 내의 현재 위치에 대한 포인터를 가집니다. 각 크기 클래스별 포인터 배열의 정적 최대 용량은 이 크기 클래스의 배열 시작과 다음 크기 클래스의 배열 시작 사이의 차이에 의해 시작 시 결정됩니다.

런타임에 CPU별 블록에 저장될 수 있는 특정 크기 클래스의 항목의 최대 수는 달라질 수 있지만, 시작 시 할당된 정적으로 결정된 최대 용량을 초과할 수 없습니다.

특정 크기 클래스의 객체가 요청되면 이 배열에서 제거되고, 객체가 해제되면 배열에 추가됩니다. 배열이 고갈되면 미들엔드에서 객체 배치를 사용하여 배열이 리필됩니다. 배열이 오버플로우하면 객체 배치가 배열에서 제거되어 미들엔드로 반환됩니다.

캐싱될 수 있는 메모리 양은 MallocExtension::SetMaxPerCpuCacheSize 매개변수에 의해 CPU당 제한됩니다. 이는 총 캐싱된 메모리 양이 활성 CPU별 캐시의 수에 따라 달라진다는 것을 의미합니다. 결과적으로 CPU 수가 많은 머신은 더 많은 메모리를 캐싱할 수 있습니다.

애플리케이션이 더 이상 실행되지 않는 CPU에 메모리를 보유하는 것을 방지하기 위해, MallocExtension::ReleaseCpuMemory는 지정된 CPU의 캐시에 보유된 객체를 해제합니다.

CPU 내에서 메모리 분배는 캐싱된 메모리의 최대 양이 제한 아래로 유지되도록 모든 크기 클래스에 걸쳐 관리됩니다. 캐싱될 수 있는 최대 양을 관리하는 것이지 현재 캐싱된 양을 관리하는 것이 아닙니다. 평균적으로 실제로 캐싱된 양은 제한의 약 절반이어야 합니다.

크기 클래스가 객체를 다 사용하면 최대 용량이 증가하고, 더 많은 객체를 가져올 때 크기 클래스의 용량을 늘리는 것도 고려합니다. 캐시가 보유할 수 있는 총 메모리(모든 크기 클래스)가 CPU별 제한에 도달하거나 해당 크기 클래스의 용량이 해당 크기 클래스의 하드코딩된 크기 제한에 도달할 때까지 크기 클래스의 용량을 늘릴 수 있습니다. 크기 클래스가 하드코딩된 제한에 도달하지 않았다면, 용량을 늘리기 위해 동일한 CPU의 다른 크기 클래스로부터 용량을 가져올 수 있습니다.

재시작 가능 시퀀스와 CPU별 TCMalloc

올바르게 작동하려면 CPU별 모드는 재시작 가능 시퀀스(restartable sequences, man rseq(2))에 의존합니다. 재시작 가능 시퀀스는 일반적인 함수와 유사한 (어셈블리 언어) 명령어 블록입니다. 재시작 가능 시퀀스의 제약은 부분적인 상태를 메모리에 쓸 수 없으며, 최종 명령어가 업데이트된 상태의 단일 쓰기여야 한다는 것입니다. 재시작 가능 시퀀스의 아이디어는 스레드가 재시작 가능 시퀀스를 실행하는 동안 CPU에서 제거되면(예: 컨텍스트 전환) 시퀀스가 처음부터 다시 시작된다는 것입니다. 따라서 시퀀스는 중단 없이 완료되거나 중단 없이 완료될 때까지 반복적으로 재시작됩니다. 이는 시퀀스 자체에서 경합을 피하면서 락이나 원자적 명령어를 사용하지 않고 달성됩니다.

TCMalloc에 대한 실질적인 의미는 코드가 TcmallocSlab_Internal_Push와 같은 재시작 가능 시퀀스를 사용하여 락이 필요 없이 CPU별 배열에서 요소를 가져오거나 반환할 수 있다는 것입니다. 재시작 가능 시퀀스는 스레드가 중단되지 않고 배열이 업데이트되거나, 스레드가 중단된 경우(예: 해당 CPU에서 다른 스레드가 실행되도록 하는 컨텍스트 전환) 시퀀스가 재시작되도록 보장합니다.

설계 선택과 구현에 대한 추가 정보는 특정 설계 문서에서 논의됩니다.

레거시 스레드별 모드

스레드별 모드에서 TCMalloc은 각 스레드에 스레드 로컬 캐시를 할당합니다. 작은 할당은 이 스레드 로컬 캐시에서 만족됩니다. 필요에 따라 객체가 미들엔드와 스레드 로컬 캐시 사이를 이동합니다.

스레드 캐시는 크기 클래스당 하나의 여유 객체 단일 연결 리스트를 포함합니다(N개의 크기 클래스가 있으면 N개의 해당 연결 리스트가 있습니다).

할당 시 객체는 스레드별 캐시의 적절한 크기 클래스에서 제거됩니다. 해제 시 객체는 적절한 크기 클래스의 앞에 추가됩니다. 언더플로우와 오버플로우는 미들엔드에 접근하여 더 많은 객체를 가져오거나 일부 객체를 반환하여 처리됩니다.

스레드별 캐시의 최대 용량은 MallocExtension::SetMaxTotalThreadCacheBytes 매개변수로 설정됩니다. 그러나 각 스레드별 캐시가 일반적으로 512KiB인 최소 크기 KMinThreadCacheSize를 가지므로 총 크기가 해당 제한을 초과하는 것이 가능합니다. 스레드가 용량을 늘리려는 경우 다른 스레드로부터 용량을 회수해야 합니다.

스레드가 종료되면 캐싱된 메모리가 미들엔드로 반환됩니다.

프론트엔드 캐시의 런타임 크기 조정

프론트엔드 캐시 여유 리스트의 크기가 최적으로 조정되는 것이 중요합니다. 여유 리스트가 너무 작으면 중앙 여유 리스트에 너무 자주 가야 합니다. 여유 리스트가 너무 크면 객체가 거기에 유휴 상태로 있어 메모리를 낭비하게 됩니다.

캐시는 할당만큼 해제에도 중요합니다. 캐시가 없으면 각 해제는 메모리를 중앙 여유 리스트로 이동해야 합니다.

CPU별 모드와 스레드별 모드는 동적 캐시 크기 조정 알고리즘의 다른 구현을 가지고 있습니다.

  • 스레드별 모드에서는 미들엔드에서 더 많은 객체를 가져와야 할 때마다 저장할 수 있는 객체의 최대 수가 제한까지 증가합니다. 마찬가지로 너무 많은 객체를 캐싱했을 때 용량이 감소합니다. 캐싱된 객체의 총 크기가 스레드별 제한을 초과하면 캐시의 크기도 줄어듭니다.
  • CPU별 모드에서는 언더플로우와 오버플로우 사이를 번갈아 하는지 여부에 따라 여유 리스트의 용량이 증가합니다(더 큰 캐시가 이 번갈아 함을 멈출 수 있음을 나타냄). 일정 시간 동안 증가하지 않았고 따라서 초과 용량일 수 있을 때 용량이 감소합니다.

TCMalloc 미들엔드

미들엔드는 프론트엔드에 메모리를 제공하고 백엔드에 메모리를 반환하는 역할을 담당합니다. 미들엔드는 Transfer cache와 Central free list로 구성됩니다. 이들은 종종 단수로 언급되지만, 크기 클래스당 하나의 transfer cache와 하나의 central free list가 있습니다. 이러한 캐시는 각각 뮤텍스 락으로 보호되므로 접근하는 데 직렬화 비용이 있습니다.

Transfer Cache

프론트엔드가 메모리를 요청하거나 반환할 때 transfer cache에 도달합니다.

Transfer cache는 여유 메모리에 대한 포인터 배열을 보유하며, 프론트엔드를 대신하여 이 배열로 객체를 이동하거나 이 배열에서 객체를 가져오는 것이 빠릅니다.

Transfer cache는 한 CPU(또는 스레드)가 다른 CPU(또는 스레드)에 의해 해제되는 메모리를 할당하는 상황에서 그 이름을 얻습니다. Transfer cache는 메모리가 두 개의 다른 CPU(또는 스레드) 간에 빠르게 흐르도록 합니다.

Transfer cache가 메모리 요청을 만족시킬 수 없거나 반환된 객체를 보유할 공간이 부족한 경우 central free list에 접근합니다.

Central Free List

Central free list는 “span”으로 메모리를 관리합니다. span은 하나 이상의 “TCMalloc 페이지”의 모음입니다. 이러한 용어는 다음 몇 섹션에서 설명될 것입니다.

하나 이상의 객체에 대한 요청은 요청이 만족될 때까지 span에서 객체를 추출하여 central free list에 의해 만족됩니다. span에 사용 가능한 객체가 충분하지 않으면 백엔드에서 더 많은 span이 요청됩니다.

객체가 central free list로 반환되면 각 객체는 (pagemap을 사용하여) 속한 span에 매핑되고 해당 span으로 해제됩니다. 특정 span에 있는 모든 객체가 해당 span으로 반환되면 전체 span이 백엔드로 반환됩니다.

Pagemap과 Span

TCMalloc이 관리하는 힙은 컴파일 시간에 결정된 크기의 페이지로 나뉩니다. 연속적인 페이지의 실행은 Span 객체로 표현됩니다. span은 애플리케이션에 전달된 큰 객체를 관리하거나 작은 객체 시퀀스로 분할된 페이지 실행을 관리하는 데 사용될 수 있습니다. span이 작은 객체를 관리하는 경우 객체의 크기 클래스가 span에 기록됩니다.

Pagemap은 객체가 속한 span을 조회하거나 특정 객체의 크기 클래스를 식별하는 데 사용됩니다.

TCMalloc은 가능한 모든 메모리 위치를 span에 매핑하기 위해 2-레벨 또는 3-레벨 기수 트리를 사용합니다.

다음 다이어그램은 radix-2 pagemap이 객체의 주소를 객체가 있는 페이지를 제어하는 span에 매핑하는 데 어떻게 사용되는지 보여줍니다. 다이어그램에서 span A는 두 페이지를 커버하고 span B는 3개의 페이지를 커버합니다.

Span은 미들엔드에서 반환된 객체를 배치할 위치를 결정하고 백엔드에서 페이지 범위 처리를 관리하는 데 사용됩니다.

Span에 작은 객체 저장

span은 span이 제어하는 TCMalloc 페이지의 베이스에 대한 포인터를 포함합니다. 작은 객체의 경우 해당 페이지는 최대 2¹⁶개의 객체로 나뉩니다. 이 값은 span 내에서 2바이트 인덱스로 객체를 참조할 수 있도록 선택됩니다.

이는 unrolled 연결 리스트를 사용하여 객체를 보유할 수 있다는 것을 의미합니다. 예를 들어, 8바이트 객체가 있으면 즉시 사용 가능한 세 객체의 인덱스를 저장하고 네 번째 슬롯을 사용하여 체인의 다음 객체의 인덱스를 저장할 수 있습니다. 이 데이터 구조는 완전히 연결된 리스트에 비해 캐시 미스를 줄입니다.

2바이트 인덱스를 사용하는 또 다른 장점은 span 자체의 여유 용량을 사용하여 4개의 객체를 캐싱할 수 있다는 것입니다.

크기 클래스에 사용 가능한 객체가 없으면 pageheap에서 새 span을 가져와 채워야 합니다.

TCMalloc 페이지 크기

TCMalloc은 다양한 “페이지 크기”로 빌드할 수 있습니다. 이는 기본 하드웨어의 TLB에서 사용되는 페이지 크기와 일치하지 않습니다. 이러한 TCMalloc 페이지 크기는 현재 4KiB, 8KiB, 32KiB, 256KiB입니다.

TCMalloc 페이지는 특정 크기의 여러 객체를 보유하거나 단일 페이지보다 큰 크기의 객체를 보유하는 그룹의 일부로 사용됩니다. 전체 페이지가 여유 상태가 되면 백엔드(pageheap)로 반환되고 나중에 다른 크기의 객체를 보유하도록 재사용되거나 OS로 반환될 수 있습니다.

작은 페이지는 더 적은 오버헤드로 애플리케이션의 메모리 요구 사항을 더 잘 처리할 수 있습니다. 예를 들어, 절반 사용된 4KiB 페이지는 2KiB가 남는 반면 32KiB 페이지는 16KiB가 남습니다. 작은 페이지는 또한 여유 상태가 될 가능성이 더 높습니다. 예를 들어, 4KiB 페이지는 8개의 512바이트 객체를 보유할 수 있는 반면 32KiB 페이지에는 64개의 객체가 있습니다. 64개의 객체가 동시에 여유 상태가 될 가능성은 8개가 여유 상태가 될 가능성보다 훨씬 적습니다.

큰 페이지는 백엔드에서 메모리를 가져오고 반환하는 필요성이 적습니다. 단일 32KiB 페이지는 4KiB 페이지의 8배 객체를 보유할 수 있으며, 이는 더 큰 페이지를 관리하는 비용이 더 작을 수 있습니다. 또한 전체 가상 주소 공간을 매핑하는 데 더 적은 큰 페이지가 필요합니다. TCMalloc은 가상 주소를 해당 주소 범위의 객체를 관리하는 구조에 매핑하는 pagemap을 가지고 있습니다. 더 큰 페이지는 pagemap이 더 적은 항목을 필요로 하므로 더 작다는 것을 의미합니다.

결과적으로 메모리 풋프린트가 작거나 메모리 풋프린트 크기에 민감한 애플리케이션의 경우 더 작은 TCMalloc 페이지 크기를 사용하는 것이 합리적입니다. 메모리 풋프린트가 큰 애플리케이션은 더 큰 TCMalloc 페이지 크기의 이점을 얻을 가능성이 높습니다.

TCMalloc 백엔드

TCMalloc의 백엔드는 세 가지 작업을 수행합니다:

  • 사용되지 않는 메모리의 큰 청크를 관리합니다.
  • 할당 요청을 만족시킬 적절한 크기의 메모리가 없을 때 OS에서 메모리를 가져오는 역할을 담당합니다.
  • 불필요한 메모리를 OS에 반환하는 역할을 담당합니다.

TCMalloc에는 두 가지 백엔드가 있습니다:

  • 레거시 pageheap: TCMalloc 페이지 크기 청크로 메모리를 관리합니다.
  • Hugepage 인식 pageheap: hugepage 크기의 청크로 메모리를 관리합니다. hugepage 청크로 메모리를 관리하면 할당자가 TLB 미스를 줄여 애플리케이션 성능을 향상시킬 수 있습니다.

레거시 Pageheap

레거시 pageheap은 사용 가능한 메모리의 연속 페이지의 특정 길이에 대한 여유 리스트의 배열입니다. k < 256의 경우 k번째 항목은 k TCMalloc 페이지로 구성된 실행의 여유 리스트입니다. 256번째 항목은 길이 >= 256 페이지를 가진 실행의 여유 리스트입니다:

k 페이지에 대한 할당은 k번째 여유 리스트를 살펴봄으로써 만족됩니다. 해당 여유 리스트가 비어 있으면 다음 여유 리스트를 살펴보는 식으로 계속됩니다. 결국 필요한 경우 마지막 여유 리스트를 살펴봅니다. 실패하면 시스템 mmap에서 메모리를 가져옵니다.

k 페이지에 대한 할당이 길이 > k의 페이지 실행에 의해 만족되면 실행의 나머지는 pageheap의 적절한 여유 리스트에 다시 삽입됩니다.

페이지 범위가 pageheap으로 반환되면 인접한 페이지를 확인하여 이제 연속 영역을 형성하는지 확인하고, 그런 경우 페이지가 연결되어 적절한 여유 리스트에 배치됩니다.

Hugepage 인식 할당자

Hugepage 인식 할당자의 목표는 메모리를 hugepage 크기 청크로 보유하는 것입니다. x86에서 hugepage는 2MiB 크기입니다. 이를 위해 백엔드에는 세 가지 다른 캐시가 있습니다:

  • Filler cache: 메모리가 일부 할당된 hugepage를 보유합니다. 이는 특정 수의 TCMalloc 페이지의 메모리의 연결된 리스트를 보유한다는 점에서 레거시 pageheap과 유사하다고 볼 수 있습니다. hugepage 크기보다 작은 크기의 할당 요청은 (일반적으로) filler cache에서 반환됩니다. filler cache가 충분한 사용 가능한 메모리를 가지고 있지 않으면 할당할 추가 hugepage를 요청합니다.
  • Region cache: hugepage보다 큰 할당을 처리합니다. 이 캐시는 할당이 여러 hugepage에 걸칠 수 있도록 하고 여러 이러한 할당을 연속 영역에 패킹합니다. 이는 hugepage 크기를 약간 초과하는 할당(예: 2.1 MiB)에 특히 유용합니다.
  • Hugepage cache: 최소한 hugepage의 큰 할당을 처리합니다. region cache와 사용에 중복이 있지만, region cache는 (런타임에) 할당 패턴이 이를 이용할 수 있다고 판단될 때만 활성화됩니다.

HPAA에서 이루어진 설계 선택에 대한 추가 정보는 특정 설계 문서에서 논의됩니다.

주의사항

TCMalloc은 시작 시 메타데이터를 위해 일부 메모리를 예약합니다. 메타데이터의 양은 힙이 커짐에 따라 증가합니다. 특히 pagemap은 TCMalloc이 사용하는 가상 주소 범위와 함께 증가하고, span은 활성 메모리 페이지 수가 증가함에 따라 증가합니다. CPU별 모드에서 TCMalloc은 CPU당 메모리 슬랩(일반적으로 256 KiB)을 예약하므로 논리적 CPU 수가 많은 시스템에서는 멀티 메비바이트 풋프린트로 이어질 수 있습니다.

TCMalloc은 OS로부터 큰 청크(일반적으로 1 GiB 영역)로 메모리를 요청한다는 점에 주목할 가치가 있습니다. 주소 공간은 예약되지만 사용될 때까지 물리적 메모리로 백업되지 않습니다. 이러한 접근 방식 때문에 애플리케이션의 VSS는 RSS보다 상당히 클 수 있습니다. 이것의 부작용은 VSS를 제한하여 애플리케이션의 메모리 사용을 제한하려는 시도가 애플리케이션이 그만큼의 물리적 메모리를 사용하기 훨씬 전에 실패할 것이라는 것입니다.

실행 중인 바이너리에 TCMalloc을 로드하려고 시도하지 마십시오(예: Java 프로그램에서 JNI 사용). 바이너리는 시스템 malloc을 사용하여 일부 객체를 할당했을 것이고 해제를 위해 TCMalloc에 전달하려고 시도할 수 있습니다. TCMalloc은 그러한 객체를 처리할 수 없습니다.


[^1]: [TCMalloc : Thread-Caching Malloc tcmalloc](https://google.github.io/tcmalloc/design) (100%)