할당자(Allocator) 선택하기
할당자(Allocator) 선택하기
어떤 할당자를 쓸지는 여러 요인에 따라 달라져요. 이걸 정할 때 참고할 수 있게 의사결정 흐름을 정리해 봤어요.
본문
어떤 할당자를 쓸지는 상황에 따라 갈려요. 아래 질문을 위에서부터 순서대로 답해 보면 자연스럽게 골라져요.
- 라이브러리를 만드는 중인가요? 이 경우엔
Allocator를 파라미터로 받아서, 라이브러리 사용자들이 자신에게 맞는 할당자를 고르도록 해 주는 게 제일 좋아요. - libc를 링크하나요? 그렇다면 (최소한 메인 할당자로는)
std.heap.c_allocator가 좋은 선택일 가능성이 높아요. - 필요한 최대 바이트 수가 comptime에 아는 수로 제한되나요? 그렇다면
std.heap.FixedBufferAllocator를 쓰면 돼요. - 프로그램이 처음부터 끝까지 흐르는 CLI 어플리케이션이고, (게임 메인 루프나 웹 서버 요청 핸들러 같은) 근본적인 주기 패턴이 없어서 끝에서 한 번에 전부 해제해도 괜찮은 상황인가요? 이 경우엔 이 패턴을 따라가는 걸 추천해요:
const std = @import("std");
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
const ptr = try allocator.create(i32);
std.debug.print("ptr={*}\n", .{ptr});
}
$ zig build-exe cli_allocation.zig
$ ./cli_allocation
ptr=i32@7f1688029018
이런 종류의 할당자를 쓰면 일일이 해제할 필요가 없어요. arena.deinit()을 호출하는 순간 전부 한 번에 해제되거든요.
- 게임 메인 루프나 웹 서버 요청 핸들러처럼 할당이 주기 패턴의 일부인가요? 그리고 그 할당들을 주기 끝(예: 게임 프레임 렌더링이 끝났을 때, 웹 서버 요청 처리 후)에 한 번에 해제할 수 있다면,
std.heap.ArenaAllocator가 훌륭한 후보예요. 위 항목에서 보여드렸듯이 아레나 전체를 한 번에 해제할 수 있거든요. 추가로 메모리 상한을 정할 수 있다면std.heap.FixedBufferAllocator로 더 최적화할 수도 있어요. - 테스트를 작성 중이고,
error.OutOfMemory가 올바르게 처리되는지 확인하고 싶은가요? 이 경우엔std.testing.FailingAllocator를 쓰면 돼요. - 테스트를 쓰는 중인가요? 그렇다면
std.testing.allocator를 써요. - 위 어디에도 해당되지 않는다면, 범용(general purpose) 할당자가 필요해요. Debug 모드라면
std.heap.DebugAllocator를 쓸 수 있는데, 이건 comptime struct 설정 옵션을 받아 타입을 돌려주는 함수예요. 보통main함수에서 딱 하나만 만들어 두고, 그 할당자나 서브 할당자들을 어플리케이션의 여러 부분에 넘겨 줘요. - ReleaseFast 모드로 컴파일 중이라면, 범용 할당자로
std.heap.smp_allocator가 탄탄한 선택이에요. - 직접 할당자를 구현하는 것도 고려해 볼 수 있어요.
핵심을 정리하면 이렇게 돼요. 라이브러리를 만든다면 할당자 선택권을 사용자에게 넘기고, 실행 파일이라면 프로그램의 수명 패턴(한 번에 전부 해제해도 되는지, 주기적으로 해제해야 하는지)에 따라 아레나나 고정 버퍼를 고르면 돼요. 마지막에 남는 경우에만 범용 할당자를 써요. 각 할당자 이름이 정확히 언제 어울리는지가 이 흐름도의 핵심이에요.