TECH 으로 돌아가기
TECH HACKER NEWS 오늘 7분 읽기 30 READS

5μs JIT 컴파일러, AI가 낮춘 진입 장벽과 데이터베이스의 야심

5μs JIT 컴파일러, AI가 낮춘 진입 장벽과 데이터베이스의 야심
SOURCE IMAGE · HACKER NEWS

JIT(Just In Time) 컴파일은 프로그램을 실행하는 도중에 기계어를 생성하는 기법이다. 실행 시점에 비로소 알게 되는 정보—예컨대 인터프리터가 넘겨받는 코드나, 미리 알 수 없는 데이터 스키마—가 프로그램의 동작을 크게 좌우할 때 특히 유용하다. 제대로 구현하면 통상 2~5배, 때로는 그 이상의 성능 향상을 기대할 수 있다. 그럼에도 JIT 컴파일러는 실무에서 좀처럼 보기 어려운 물건이었다. 원문 저자는 그 이유를 '역사적으로 JIT을 잘 짜려면 어셈블리를 직접 다룰 줄 알아야 했고, 그것이 곧 흑마법에 가까웠기 때문'이라고 짚는다.

실제로 오늘날 상용 데이터베이스 가운데 자체 JIT 컴파일러를 갖춘 제품은 사실상 없다. 대부분 LLVM을 끌어다 쓰거나 C/C++ 코드를 생성하는 방식을 택한다. 두 방법 모두 컴파일 시간이 길다는 공통의 약점을 안고 있어, JIT을 쓸 수 있는 상황 자체가 제한된다. 쿼리 하나를 실행하려고 무거운 컴파일 파이프라인을 돌리는 것이 수지가 맞지 않기 때문이다. 저자가 만들고 있는 pgrust는 바로 이 지점을 공략한다. pgrust의 JIT 컴파일러는 코드를 약 5μs 만에 컴파일하며, 그 덕분에 일부 쿼리가 아니라 '모든 SQL 쿼리'를 JIT 컴파일할 수 있다고 한다.

카피 앤 패치라는 실용적 지름길

이 빠른 컴파일의 핵심은 '카피 앤 패치(copy-and-patch)'라 불리는 접근의 변형이다. 발상은 단순하다. JIT으로 만들고 싶은 연산마다 미리 준비해 둔 어셈블리 템플릿, 이른바 '스텐실(stencil)'을 두는 것이다. 특정 연산을 컴파일할 때는 해당 스텐실을 가져와 세부 값만 살짝 채워 넣는다. 물감을 스텐실 구멍에 대고 찍어내듯, 이렇게 채워진 조각들을 이어 붙이면 실행 시점에 손으로 짠 코드에 가까운 성능의 프로그램이 만들어진다. 복잡한 최적화 컴파일러를 돌리는 대신, 검증된 코드 조각을 조립하기 때문에 컴파일이 극도로 빠르다.

원문은 이 개념을 설명하기 위해 장난감 수준의 정규식 엔진을 예로 든다. 리터럴 문자열과 반복()만 지원하고 파서는 생략한 채, 정규식을 이미 파싱된 Rust 구조체로 표현한다. 노드는 리터럴 문자열, 반복, 그리고 둘을 잇는 연결(concatenation) 세 가지뿐이다. 이 엔진의 인터프리터는 20줄이 채 안 될 만큼 간단하지만, 성능은 그만큼의 대가를 치른다. b(an) 같은 정규식을 위해 손으로 특화해 짠 코드와 비교하면, 인터프리터가 10~20배 느렸다는 것이 저자의 벤치마크 결과다.

생성된 코드에서 거꾸로 짜 내려가기

저자는 컴파일러를 설명하는 순서를 뒤집는다. 먼저 b(an)*에 대해 생성하고 싶은 ARM64 어셈블리(macOS 기준)를 통째로 보여주고, 거기서 반복되는 명령 시퀀스를 재사용 가능한 스텐실로 추려낸 뒤, 정규식 AST로부터 스텐실을 채우고 이어 붙이는 이미터를 작성하는 식이다. 생성된 코드는 스택을 초기화하는 프롤로그, 문자 'b'를 검사하고 일치하면 위치를 전진시키는 블록, 백트래킹을 위해 루프 종료 주소와 문자열 위치를 스택에 저장하는 반복 처리, 문자열 끝 도달 여부로 성공/실패를 가르는 매치 블록, 그리고 스택에서 대체 주소를 꺼내 되돌아가는 폴백 로직으로 구성된다. 이렇게 블록마다 공통 골격에 미세한 차이만 있다는 점이 드러나면, 각 블록을 생성하는 함수—비교할 문자를 인자로 받는 stencil_char 같은—로 자연스럽게 정리된다.

마지막 조립 단계는 운영체제 레벨의 작업이다. mmap으로 읽기·쓰기·실행이 모두 가능한 메모리 블록을 할당하고, 생성한 기계어를 그 안에 복사한 뒤, 그 메모리를 함수 포인터로 변환해 Rust에서 일반 함수처럼 호출한다. 이 과정을 거친 JIT 버전과 손으로 짠 구현의 성능은 사실상 백중지세였다. 어떤 예제에서는 JIT이, 다른 예제에서는 손코딩이 근소하게 앞섰다. 인터프리터 대비 10~20배의 격차를, 범용 엔진을 유지한 채 메워낸 셈이다.

AI가 바꾼 것은 '코드를 짜는 일' 그 자체

주목할 대목은 저자가 어셈블리를 스스로 써 본 경험이 사실상 없다고 밝히는 부분이다. microcorruption CTF를 풀어 본 것이 전부인데도, AI 코딩 에이전트에 JIT 컴파일러의 대략적 형태만 지시하면 구체적인 명령어 선택과 수정은 도구가 처리해 주었다고 한다. 그는 'AI는 도움이 안 된다, 코드가 어려운 적은 없었으니까'라는 밈을 겨냥해, 어떤 영역에서는 코드를 짜는 것 자체가 바로 어려운 부분이었다고 반박한다. JIT 컴파일러가 그 대표 사례다.

다만 이 글은 상용 제품의 검증된 사례가 아니라 저자 개인의 pgrust 개발 경험과 장난감 예제에 기반한 주장임을 감안할 필요가 있다. 예제 정규식 엔진은 대안(alternation)이나 룩비하인드조차 지원하지 않는 최소 구현이고, 벤치마크의 구체적 수치나 환경 상세는 원문에 충분히 제시되지 않았다. 스텐실 방식은 조립의 유연성을 얻는 대신 LLVM 같은 본격 최적화 컴파일러가 수행하는 저수준 최적화는 포기하는 트레이드오프도 안고 있다. 그럼에도 실무자 입장에서 새겨둘 메시지는 분명하다. JIT이 드물었던 이유가 '가치가 없어서'가 아니라 '구현이 너무 어려워 수지가 맞지 않아서'였다면, 그 진입 장벽이 낮아지는 순간 데이터베이스처럼 오래되고 무겁던 소프트웨어 영역에서도 더 야심 찬 설계를 시도해 볼 여지가 열린다는 점이다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://malisper.me/jit-compiling-code-in-5-us/
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기
처리 중...