x32에서 실행한 Janet: 32비트 포인터, 64비트 속도, RAM 25% 절감
요약
본 기사는 x32 아키텍처의 재조명과 메모리 효율성을 다룹니다. 과거에는 주소 공간 부족이 문제였으나, 이제는 자원 증가로 인해 오히려 32비트 포인터가 제공하는 제한된 주소 공간이 특정 상황에서 이점을 가질 수 있음을 논합니다. Zig 언어의 x32 지원 추가와 크로스 컴파일 테스트를 통해 그 가능성을 탐구하고 있습니다.
핵심 포인트
- x32 아키텍처는 메모리 효율성 측면에서 재조명되고 있음.
- 64비트 시스템에서도 32비트 포인터가 특정 환경(예: 작은 객체)에서 이점을 가질 수 있음.
- Zig 언어 등에서 x32 지원이 추가되며 관련 기술적 가능성이 높아지고 있음.
LWN에서 Corbet이 또다시 제안된 x32 제거를 다루며 “메모리 사용량이 늘어나 이제 기본적인 ‘hello world’ 앱조차 32비트 포인터가 제공하는 2~3GB 가상 주소 공간에 욱여넣기 어려워졌다”고 했음.
반도체 부족의 긍정적인 부작용으로 메모리 절약이 다시 주목받을 수도 있겠음. 메모리 용량이 계속 두 배로 늘어났다고 해서 소프트웨어가 전부 써야 하는 것은 아님. “쓰지 않는 RAM은 낭비되는 RAM”이라고 해도 마찬가지임.
Arch에서 실행에 실패한 뒤 컴파일 지원이 어떻게 되어 있어야 하는지 알아보다가 -mx32 를 처음 알게 됨. 조금 늦었을지 몰라도 사용을 늘리고, @hailey가 Mastodon 등에서 발견한 큰 성능 향상이 어떤 조건에서 나오는지 확인하고 싶음.
그래서 글에 행동을 촉구하는 문구를 추가함. Linux 배포판은 -mx32를 지원하고, 데몬과 유틸리티도 이 옵션으로 컴파일해 유휴 상태와 일상적인 컴퓨팅의 효율을 높여야 함. 필요한 것은 플래그 하나뿐임.
“hello world 앱이 32비트 주소 공간에 들어가지 않는다”는 건 농담인가? 농담이라고 생각했지만, 사실 물어보기도 두려움.
Zig 0.17.0에 x32 완전 지원이 막 들어감. libc 없이 직접 시스템 호출을 사용하거나, glibc 또는 musl을 사용할 수 있음. CI에서도 네이티브로 테스트하며, Debian은 여전히 커널 명령줄의 syscall.x32=y를 지원함. MIPS의 대응 ABI인 N32 지원도 추가했지만, 그쪽에 관심을 가질 사람은 적을 듯함.
주로 재미 삼아 구현했고 CI에서 포인터 크기에 대한 잘못된 가정을 잡아내려는 목적도 있었는데, 실제로 유용하게 쓰이기까지 한다면 좋겠음.
지금 zig cc -target x86_64-linux-muslx32 로 크로스 컴파일을 시험 중인데, 잘되면 배포 과정이 훨씬 수월해질 것 같음.
2년 전에 이 주제를 더 자세히 다룬 글을 썼음. https://blogsystem5.substack.com/p/x86-64-programming-models . 표지의 AI 생성 이미지가 별로인 건 알지만, 본문은 전부 직접 조사하고 작성함.
amd64가 등장했을 당시에도 커진 포인터로 인한 메모리 사용량 증가를 우려했음. 자원이 계속 늘어났기에 대수롭지 않게 여겼던 듯함. 이제 구매 가능한 자원에 한계가 생겼으니, 당시 선택을 다시 검토해 볼 만함.
그 표지 때문에 글까지 저품질 AI 생성물로 오해받는다는 걸 알면서 왜 그대로 두는 건가? 글을 소개할 때마다 표지부터 미리 변호하는 게 좋은 건가?
RAM 절감 폭이 아주 커 보이지만, 그런 헤더 구조를 쓰고 작은 객체가 많은 언어라면 가능한 결과 같음. CHERI 관련 분석에서는 포인터가 하나라도 들어 있는 페이지가 대체로 20% 미만이었음. 메모리의 상당 부분은 큰 버퍼, 이미지 등 포인터가 없는 데이터가 차지함.
이것은 x32가 거의 사라진 주요 이유이기도 함. 개별 프로세스는 작아져도 32비트·64비트 공유 라이브러리를 함께 유지하는 데 드는 디스크와 RAM까지 계산하면 이점이 불분명해짐. 모든 것이 x32를 쓰면 아마 이득이겠지만, JavaScript 같은 스크립트 언어는 NaN 박싱을 선호하므로 32비트가 유리하지 않음. WebAssembly 같은 경량 소프트웨어 결함 격리(SFI) 기술도 주소 공간에서 4GiB를 확보한 뒤 포인터를 그 크기로 잘라 쓰면 잘 동작함. 필요하면 기준 오프셋도 더할 수 있어, Node.js와 웹 브라우저는 64비트 시스템을 원하게 됨.
JVM도 비슷함. 세대별 GC를 위해 힙을 두 곳 이상에 매핑할 수 있으면 편리하고, Java 객체용으로 다른 영역과 겹치지 않는 넓은 주소 범위도 필요함. 따라서 사용자 주소 공간이 2GiB라면 힙은 300MiB 정도로 제한될 수 있음. 반면 64비트 JVM은 보통 힙 기준 주소로부터의 오프셋을 3~4비트 오른쪽으로 시프트한 32비트 정수로 객체를 표현하므로, 포인터 크기 증가 부담 없이 64GiB 힙을 사용할 수 있음. NIO 버퍼와 큰 배열은 별도 힙 영역에 두고 작은 객체 헤더만 64GiB 힙에 남기는 방법도 있어, 실질적인 포인터 크기 부담 없이 힙을 1TiB의 상당 부분에 이르는 크기로 확장할 수 있음.
VM처럼 프로세스 힙 할당의 일부에만 32비트 포인터를 쓰려는 목적이라면, 시스템 ABI를 크게 바꾸지 않아도 됨. Linux의 mmap에서는 MAP_32BIT 로 쉽게 구현할 수 있음. 그런 기능이 없는 운영체제에서는 서로 다른 기준 주소와 할당 크기로 MAP_FIXED를 시도해 흉내 낼 수 있음. 가능하면 프로그램 시작 직후 0..2^32 주소 범위에서 영역을 확보하고, 그 안에서 다시 나눠 할당하면 됨.
이 방식에서는 32비트 상대 오프셋이나 아레나 인덱스와 마찬가지로, 저장용 작은 포인터 표현에 표준 포인터 타입을 쓰지 않게 됨. 저장용 표현과 실제 연산용 표현을 오갈 때 포인터의 출처 정보(provenance)를 온전히 보존하지는 못하지만, 바로 그 경계에서는 출처 정보가 최적화 수단으로 그다지 유용하지 않음. 레지스터와 스택에 놓이고 함수 경계를 넘나들며 VM의 FFI 대상에 전달되는 실제 연산용 표현은 일반 포인터 타입을 사용할 수 있고, 그렇게 해야 함.
AI 자동 생성 콘텐츠
본 콘텐츠는 GeekNews의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기