Post on 17-Jul-2015
명시적 할당기 요구사항
임의의 요청 순서를 처리
•할당과 반환의 순서에 대해 가정하지 말 것
요청에 즉시 응답하기
•요청의 버퍼링, 재정렬 등을 수행하지 못함
힙만 사용
•할당기 자체 확장성을 위해 사용하는 자료구조는 힙 자체에
명시적 할당기 요구사항
블록 정렬하기
•어떤 종류의 데이터도 저장할 수 있어야 함
•더블워드, 8바이트 경계로 정렬
할당된 블록을 수정하지 않기
•가용 블록의 수정은 가능하나, 이미 할당된 블록을 수정해서는 안 됨
그러나, OSX 10.9에서는 Inactive메모리 압축을 도입하여 시스템 응답성 50%, 웨이크업 속도 40% 향상
가장 쉬운 할당기
구현
•힙을 하나의 커다란 바이트 배열과 이 배열의 첫 번째 바이트를 가리키는 포인터 p로 구성
• size 바이트를 할당하기 위해 malloc은 현재 p값을 스택에 저장하고 p를 size만큼 증가, p이전 값을 호출자에 리턴
• free는 아무 것도 하지 않음
특징
•빠르지만(처리량), 메모리 많이 사용(이용도)
묵시적 가용 리스트
Block size 0 0 a
Payload (allocated block only)
Padding (optional)
8bytes aligned a = 1: allocated a = 0: free
The block size includes the header, payload, and any padding
malloc returns a pointer to the beginning of the payload
8/0 16/1 32/0 16/1 0/1
double-word aligned
size(bytes)/allocated bit
블록 배치 전략
first fit
•첫 번째로 매치하는 것을
•큰 블록은 뒤에 남는 경향이 있음
•이로 인해 큰 블록을 할당하는데 상대적으로 많은 시간이 걸릴 수 있음
best fit
•가장 잘 맞는 것
•좋지만, 힙 전체를 검색해야함
블록의 배치 전략
next fit
• first fit의 대안으로 Knuth가 제안
•목록의 앞에 작은 블록이 있다면 first fit 보다 많이 빠름
•하지만 일부 연구에서 최악의 메모리 이용 결과가
블록의 분할과 연결
연결
8/0 16/1 32/0 16/1 0/1
8/0 16/1 16/1 16/0 16/1 0/1
8/0 16/1 16/0 16/0 16/1 0/1
8/0 16/1 32/0 16/1 0/1
while (...) { p = malloc(16) // ... free(p)}
블록의 분할과 연결
스래싱(thrashing)
•분할과 연결의 반복
8/0 16/1 32/0 16/1 0/1
8/0 16/1 16/1 16/0 16/1 0/1
8/0 16/1 16/0 16/0 16/1 0/1
8/0 16/1 32/0 16/1 0/1
while (...) { p = malloc(16) // ... free(p)}
블록의 분할과 연결
다시 연결
•어떻게 바로 뒤 블럭 정보를 알 수 있을까?
8/0 16/1 16/0 16/1 16/1 0/1
8/0 16/1 16/0 16/0 16/1 0/1
?
블록의 분할과 연결
경계 태그
• Knuth가 제안
•블록의 끝에 푸터를 추가
•푸터 만큼 오버헤드
8/0 16/1 16/0 16/1 16/1 0/1
8/0 16/1 16/0 16/0 16/1 0/1
Block size 0 0 a
Payload (allocated block only)
Padding (optional)
Block size 0 0 a
블록의 분할과 연결
경계 태그
•할당된 블록에서는 푸터 필요 없음
•대신 앞 블록의 할당여부 플래그 필요
Block size 0 b a
Payload (allocated block only)
Padding (optional)
Block size 0 b a
Current block a = 1: allocated a = 0: free
Previous block b = 1: allocated b = 0: free
명시적 가용 리스트
Block size 0 b a
Payload (allocated block only)
Padding (optional)
Block size 0 b a
Block size 0 b a
Predecessor
Successor
Padding (optional)
Block size 0 b a
allocated block free block
명시적 가용 리스트
반환
•반환하는 블록을 리스트의 시작 부분에 삽입(LIFO, first fit)
상수 시간에 반환 가능
경계 태그를 이용하면 연결도 상수 시간
•리스트를 주소 순으로 관리
best fit에 알맞음
분리 가용 리스트
기본 사상
•할당 요청 받는 크기별로 가용 리스트를 관리
•그 크기별 묶음을 크기 클래스(size class)라고 함
•크기 클래스 정의는 . 2의 제곱수: {1}, {2}, {3, 4}, {5-8}, {1025-2048}, ... . 작은 크기는 자신의 크기 클래스로, 큰 블록은 2의
제곱수로: {1},{2}, {3}, {4}, ..., {1023}, {1024}, {1025-2048}, ...
간단한 분리 할당기
한 클래스에는 같은 크기의 블럭을
• {17-32}의 경우 리스트에는 모두 32바이트짜리 블럭
first fit
분할 없음
리스트가 비었을 경우, 운영체제에 추가 메모리 할당 요구(좀 크게) .받아서 해당 클래스 크기에 맞게 분할해서 리스트
의 맨 앞에 추가
간단한 분리 할당기
한 클래스에는 같은 크기의 블럭을 할당
할당, 반환이 모두 상수 시간
블록당 오버헤드가 거의 없음
•동일한 크기, 분할 불가, 연결 불가
•이중 연결 필요 없음: 할당은 first fit, 반환은 맨 앞 삽입
•다음을 가리키는 포인터만 필요
내외부 단편화에 취약
분리 맞춤(Segregated Fits)
한 클래스에 다른 크기의 블럭을
• first fit 후 남는 것은 분할하여 적당한 크기 클래스의 리스트에 추가
•최적 크기 클래스의 리스트가 비었을 경우, 다음 클래스에 가서 찾음
•모든 클래스에서 못 찾으면 힙 메모리를 운영체제에 요청하고 할당 후 분할하여 적절한 크기 클래스에 추가
•반환 시 적절한 가용 리스트에 추가
분리 맞춤(Segregated Fits)
한 클래스에 다른 크기의 블럭을
GNU malloc
가용 리스트의 단순한 first fit 검색이 전체 힙을 best fit 검색하는 것을 단순화한 것과 비슷
1024K
A 128K 256K 512KRequest 100K
1024K를 절반으로 나누면서 요청 받은 100K에 가장 적합한 블록을 찾음
512K는 100K에 비해 너무 커, 다시 나눔
이 과정을 반복
1024K
A 128K 256K 512K
A B
A C 64K B
A C B D
A C D
Request 100K
Request 240K
Request 64K
Request 256K
Release B
B를 반환한 다음, 자기의 나머지 반 쪽(buddy) 256K 영역을 확인 그곳이 비어 있다면 합침
현재 A, C가 있어 합치지 않음
1024K
A 128K 256K 512K
A B
A C 64K B
A C B D
A C D
C D
Request 100K
Request 240K
Request 64K
Request 256K
Release B
Release A
1024K
A 128K 256K 512K
A B
A C 64K B
A C B D
A C D
C D
E C D
Request 100K
Request 240K
Request 64K
Request 256K
Release B
Release A
Request 75K
1024K
A 128K 256K 512K
A B
A C 64K B
A C B D
A C D
C D
E C D
E D
Request 100K
Request 240K
Request 64K
Request 256K
Release B
Release A
Request 75K
Release C
C를 반환하고 나머지 반 쪽이 비어 있어 합침
1024K
A 128K 256K 512K
A B
A C 64K B
A C B D
A C D
C D
E C D
E D
D
Request 100K
Request 240K
Request 64K
Request 256K
Release B
Release A
Request 75K
Release C
Release E
E를 반환하고 나머지 반 쪽이 비어합치고 보니 (128K+128K), 합친 것의 반 쪽도 비어 있어 합침 (256K+256K)
1024K
A 128K 256K 512K
A B
A C 64K B
A C B D
A C D
C D
E C D
E D
D
Request 100K
Request 240K
Request 64K
Request 256K
Release B
Release A
Request 75K
Release C
Release E
Release D
버디 시스템
외부 단편화에 대한 해결책
• 1963년 Harry Markowitz가 고안(1990년 노벨경제학상)
시뮬레이션 랭귀지 프로그래밍 분야에 영향을 미침
SIMSCRIPT라는 언어에 버디 메모리 할당을 도입
• 2의 제곱 단위로 실 할당 크기가 맞아 떨어짐
•단점
내부 단편화를 해결하지는 못함
62K를 요청하면 64K를 할당 받기 때문에 2K가 낭비
리눅스 커널의 내부 단편화 해결책
슬랩 할당자
•버디 시스템은 1965년, 슬랩 할당자는 1994년에 소개
30년...
•버디 시스템에서 받아온 영역을 내부 단편화를 최소로 알뜰히 사용하기 위한 방법
슬랩 할당자
기본 원칙
자주 사용하는 자료구조는 할당과 해제가 빈번하므로 캐쉬(이런 할당/해제 패턴은 단편화 유발함)
단편화를 막기 위해 리스트는 연속된 순서대로 정리. 해제하면 해제 리스트로 들어가서 단편화 없음
바로 다음 할당 시 해제 리스트에서 꺼내 사용
할당자가 객체 크기, 페이지 크기, 전체 캐쉬 크기를 알면 보다 정교한 처리가 가능
struct kmem_cache *kmem_cache_create(const char *name, // 캐시 이름
size_t size, // 캐시에 들어갈 항목의 크기size_t align, // 정렬 방식unsigned long flags, // 캐시 동작 제어void (*ctor)(void*)); // 캐시 생성자
align
• SLAB_HWCACHE_ALIGHN
캐쉬 라인에 맞춰 정렬
메모리 사용량 증가
• SLAB_POISON, SLAB_RED_ZONE
struct kmem_cache *kmem_cache_create(const char *name, // 캐시 이름
size_t size, // 캐시에 들어갈 항목의 크기size_t align, // 정렬 방식unsigned long flags, // 캐시 동작 제어void (*ctor)(void*)); // 캐시 생성자
슬랩 할당자
기본 원칙
프로세서 단위 캐쉬가 있다면 SMP 잠금 없이 할당/해제 가능
할당자가 NUMA를 지원하는 경우, 메모리를 요청한 노드에 있는 메모리를 할당
여러 객체가 같은 캐쉬에 섞이지 않도록
메모리 할당자 만들기
개선책
•스레드별로 별도의 가용 리스트를
•어차피 스레드는 CPU 개수에 따라 효율성이 결정됨
스레드 당 1개가 아닌 스레드 묶음별로 리스트를
•전체적으로 잠금 경쟁 횟수를 줄일 수 있음
스레드 그룹별로만 잠금 결쟁
arena CPU 1개면 1개 2보다 클 때 CPU개수*4개
small (size class) [8], [16, 32, 48, ..., 128], [192, 256, 320, ..., 512], [768, 1024, 1280, ..., 3840]
large (size class) [4K, 8K, 12K, ..., 4072K]
arena 논리적으로 2^k 크기의 chunk로 메모리가 나뉘어져 있음 (기본 크기는 4M)
메모리 할당자 만들기
캐쉬가 크면, 특정 경우에는 좋지만 일반적으로 단편화가 발생하여 좋지 않을 수 있음
단편화를 제한하기 위해, 캐쉬에 대해 점증 적 GC를 수행
한 번이상 GC 수행할 동안에 사용되지 않은 캐쉬된 객체는 그 양에 비례하여 arena로 점차 옮겨짐
tcache 10~100배의 싱크 이벤트 감소 효과