[PostgreSQL] palloc
POSTGRES의 memory allocator
palloc.h
- 백엔드 대부분의 모듈에서 사용하는 메모리 할당 인터페이스를 제공한다.
postgres.h
에 포함되어 있기 때문에 이 파일이 포함하는 정의는 모든 곳에서 접근 가능하다. 가볍게 만들어야 한다.- 메모리 할당은 항상 "context" 내부에서 진행된다.
palloc()
또는MemoryContextAlloc()
를 통해 반환받은 chunk는 항상 하나의 context 내부에서 가져온(할당한) 것이다.- context의 모든 데이터는 context를 reset 또는 delete 하여 쉽게 해제할 수 있다. 이 방법은 chunk 각각을 해제하는 것보다 더 빠르고 메모리 누수 버그에 덜 취약하다.
- context는 트리 형태로 관리한다.
- 자세한 내용은 mmgr 참고
MemoryContextCallback
/*
* MemoryContextData는 nodes/memnodes.h에 정의되어 있다.
* 대부분의 사용자는 MemoryContextData를 추상 클래스로 사용하도록 설계되었다.
*/
typedef struct MemoryContextData *MemoryContext;
/*
* memory context에는 콜백함수들을 등록할 수 있다.
* 등록된 콜백함수는 리셋 또는 삭제되기 직전에 한 번 호출된다.
* MemoryContextCallback 인스턴스가 저장될 공간은 컨텍스트 내부에서 스스로 할당하기 때문에
* 외부에서 명시적으로 해제할 필요가 없다. 해당 컨텍스트를 해제/삭제하면 자동으로 해제된다.
*/
typedef void (*MemoryContextCallbackFunction) (void *arg);
typedef struct MemoryContextCallback
{
MemoryContextCallbackFunction func; /* 콜백함수 */
void *arg; /* 콜백에 전달할 인자 */
struct MemoryContextCallback *next; /* 다음으로 호출할 콜백함수. 리스트로 연결되어 있다. */
} MemoryContextCallback;
CurrentMemoryContext
/*
* palloc이 메모리를 할당하는 기본 메모리 컨텍스트.
* 직접 이 객체에 접근하는 것은 위험하다. 컨텍스트를 바꾸고싶다면
* MemoryContextSwitchTo 함수를 이용해라.
*/
extern PGDLLIMPORT MemoryContext CurrentMemoryContext;
PGDLLIMPORT
- loadable module이 core build에 선언된 변수에 접근할 수 있도록 허용하는 매크로
__declspec
Windows에서 제공하는 C/C++ 전용 확장 기능이다. 스토리지 클래스 정보
를 지정하는데 사용한다.
__declspec(dllexport)
DLL에 정의된 함수에 이 특성(attribute)을 표시함으로써 그 함수가 외부로 노출되는 인터페이스임을 명시적으로 나타낸다. DLL에서 dllexport
로 선언된 함수에 대해서는
별도의 .def
파일을 만들어주지 않아도 된다.
__declspec(dllimport)
DLL에 선언된 symbol을 사용하는 것을 import한다고 표현한다. 함수에 대해서는 헤더파일을 include하는 것만으로도 해당 함수를 호출할 수 있지만
변수에 접근하기 위해서는 반드시 해당 symbol에 __declspec(dllimport)
가 적용되어 있어야 한다. 함수에도 사용하는 것이 좋다. 컴파일러가
더 효율적인 코드로 번역할 수 있기 때문이다.
/*
* defines for dynamic linking on Win32 platform
*/
/*
* Variables declared in the core backend and referenced by loadable
* modules need to be marked "dllimport" in the core build, but
* "dllexport" when the declaration is read in a loadable module.
* No special markings should be used when compiling frontend code.
*/
#ifndef FRONTEND
#ifdef BUILDING_DLL
#define PGDLLIMPORT __declspec (dllexport)
#else
#define PGDLLIMPORT __declspec (dllimport)
#endif
#endif
MemoryContextAlloc
- 왜
MemoryContextAlloc
함수에서 직접 메모리 할당 실패에 대한 처리를 하면 비효율적일까?- if문의 수가 하나 더 늘어난다. 이미 MemoryContextMethods 에서 메모리 컨텍스트에 남는 메모리가 있는지 확인하느라 if문을 사용해서 결과를 반환했는데 MemoryContextAlloc에서 반환 결과가 NULL인지 확인하는 if문이 추가된다
- MemoryContextMethods에서만 할당 실패 처리를 하면 메모리 컨텍스트에서 더이상 할당이 불가능하다고 판단한 if문 내부에서 malloc을 호출하면 되기 때문이다. 물론 여기에서도 실패하는지 확인하기 위해 if문을 쓰긴 하는데 이 if문은 실패할 확률이 낮아서 unlikely 같은 api로 최적화가 가능할 것 같다
alloc
에는 런타임에 어떤 함수가 저장되어 있는걸까?
/*
* MemoryContextAlloc
* Allocate space within the specified context.
*
* This could be turned into a macro, but we'd have to import
* nodes/memnodes.h into postgres.h which seems a bad idea.
*/
void *
MemoryContextAlloc(MemoryContext context, Size size)
{
void *ret;
Assert(MemoryContextIsValid(context));
AssertNotInCriticalSection(context);
context->isReset = false;
/*
* 효율성을 위해 메모리 할당 실패 처리를 MemoryContextMethods 구현부로
* 의도적으로 위임했다. 이렇게 하면 OS에게 메모리를 더 요청하기 위해 malloc을
* 실제로 호출해야 할 때에만 이런 검사를 수행하기 때문이다.
* 또한 이 함수 이후에 어떠한 연산(insturction)도 할 필요가 없다는 점은
* 컴파일러가 sibling call 최적화를 하도록 유도한다.
* 이 함수가 반환한 뒤에 실행될 코드를 추가하기 전에 함수를 할당하는 함수(`alloc`) 내부에서
* 해당 작업을 실행하도록 수정하는 것을 검토해라.
*/
ret = context->methods->alloc(context, size, 0);
VALGRIND_MEMPOOL_ALLOC(context, ret, size);
return ret;
}
Tail calls
어떤 함수에서 실행하는 마지막 연산이 함수일때 이 호출되는 함수(bar
)를 tail call이라고 한다.
int foo(int a) { /* some code */ return bar(b); }
Tail Recursive Calls
Tail call의 특수한 케이스 중 하나로, callee와 caller가 동일한 함수인 경우이다.
Sibling Calls
Tail call의 특수한 케이스로 중 하나로, callee와 caller가 서로의 스택을 재활용 할 수 있는 경우이다.
caller의 스택을 callee가 재사용하여 스택 영역 계산에 필요한 시간을 줄일 수 있다
AssertNotInCriticalSection
- 여기에서 말하는 패닉이란 어떤 상황일까...
/*
* critical section에서 메모리를 할당하지 못하도록 막는다. critical section
* 내에서의 out-of-memory 에러는 패닉으로 확대될 수 있기 때문이다.
*/
#define AssertNotInCriticalSection(context) \
Assert(CritSectionCount == 0 || (context)->allowInCritSection)
references
- PostgreSQL github repository
- src/include/utils/palloc.h
- __declspec
- Exporting from a DLL Using __declspec(dllexport)
- [Intel C++ Compiler Classic Developer Guide and Reference] foptimize-sibling-calls
- [stackoverflow] What does "sibling calls" mean?