go 쪼렙입니다. 며칠 안 됐어요.
시작.
go에서는 mmap을 다루기가 힘들다. go의 이념을 아직 알 수는 없으나 대충 파악한 이념에 따르면 go는 스택과 힙을 이념적으로 매우 잘 쓰고 있다. 개발자의 고통을 덜어주면서도 알아서 힙에 갈 놈과 스택에 갈 놈을 잘 나누고, GC를 해준다.
그런데 mmap의 경우는 조금 다른데, 이게 mmap을 잘 쓰기가 어렵다.
예를 들어 디스크에 대용량 파일을 mmap을 잡아서 구조체 포인터로 할당받아 돌아다니는 상황이다. go의 mmap은 []byte를 리턴한다. 나는 포인터 형식으로 구조체를 다루고 싶은데 go는 캐스팅이 없고 컨버전만 있다. 복사가 일어나거나 해서 위치가 바뀐다. 어찌어찌 컨버전한 내용을 수정하면 어찌 될까. 이미 복사된 주소이고 mmap으로 사상된 주소가 아니므로 파일에 반영되지 않는다. 게다가 애초에 대용량 파일이었으니 이걸 복사한다고 낑낑댄 것은 자동이다. 게다가 커널이 알아서 페이지 캐시를 잘 써주겠지 하는 기도도 무용이 된다.
이제는 거울 앞에 선 C 프로그래머들은 난관에 부딪친다. 야. 이거 어쩌라고. 하지만 답이 있다.
header := (*reflect.SliceHeader)(unsafe.Pointer(&mmap))
header.Len /= UINT_BYTE_COUNT
header.Cap = header.Len
map_uint := *(*[]uint)(unsafe.Pointer(header))
unmap은 반대로 하면 된다.
header := (*reflect.SliceHeader)(unsafe.Pointer(&mmap))
header.Len *= UINT_BYTE_COUNT
header.Cap = header.Len
unmap := *(*[]byte)(unsafe.Pointer(header))
다음을 참조하자.
https://stackoverflow.com/questions/9203526/mapping-an-array-to-a-file-via-mmap-in-go
https://stackoverflow.com/questions/18071112/how-to-mmap-a-slice-of-x-in-go
나는 아직 reflect, unsafe를 까본 적이 없어서 자세한 설명은 생략한다. 유추할 수 있는 것은 go의 슬라이스는 자료형의 메타를 관리하고 있고, 어떤 반조립 슬라이스에 메타를 채우거나 바꾸면 다른 형의 슬라이스로 쓸 수 있다는 것이다. 이렇게 슬라이스의 컨버전이 아닌 캐스팅이 가능하다.
이렇게 하여 mmap으로 할당받은 []byte 슬라이스를 []uint 슬라이스로 다룰 수 있게 됐다. 구조체를 정의해서 사용하면 구조체 슬라이스로 사용할 수 있을 것이다.
여기까진 됐는데 이렇게 사용하는 go의 mmap이 c의 mmap 처럼 동작할지는 아직 모른다. 아마도 함수들을 오갈 때 슬라이스가 포인팅하는 위치의 구조체를 복사 없이 잘 쓸 수는 있을 것 같은데, 경우에 따라 스택 복사나 힙 복사에 의해서 처리될 가능성이 있다. 이러면 페이지 캐시 직빵 힛트 확률이나 커널이 알아서 수행하는 프리펫치 확률도 떨어질 것 같다.
생각해보니 비어있는 인터페이스로 다룰 수 있을 것도 같다. 성능에는 어떤 영향을 줄지 모르겠다. 인터페이스를 거치면 힙으로 쓰리쿠션 탈 것 같은데.. 공부해보고 4주후에 뵙겠습니다.