본문으로 건너뛰기
0
r/jeffnews HN 약 7분

Monus와 힙 — 차이만 저장하면 됨이라는 대수적 통찰

backend

요약

Monus(부분 뺄셈을 지원하는 모노이드)를 이용해 pairing heap을 구현하는 아이디어를 설명한 Haskell 포스트. 핵심 통찰은 '절댓값 대신 부모와의 차이를 저장하라'는 것으로, 이를 통해 힙 불변식을 타입 수준에서 강제하고 전체 가중치 수정을 O(1)로 만들 수 있음. 이 패턴을 Phases 어플리케이티브, 안정 정렬을 위한 Key monus, 모나딕 힙으로까지 확장함.

기사 전체 정리

Monus와 힙 — "차이만 저장하면 됨"이라는 대수적 통찰

원문: Monuses and Heaps | HN

Monus가 뭔지부터

Monus는 부분 뺄셈을 지원하는 모노이드임. 정확히는 순서가 붙은 모노이드(ordered monoid)인데, 그 순서가 모노이드 연산 자체로 정의됨:

x ≤ y  ⟺  ∃z. y = x • z

"x가 y보다 작거나 같다"는 말은 "x와 y 사이의 간격 z가 존재한다"는 뜻임. 즉, 그 간격이 반드시 원소로서 실재해야 함.

왜 그룹(group)은 안 되냐면 — 정수 (ℤ, +, 0) 같은 그룹에서는 음수가 있어서 어떤 x, y에 대해서도 항상 z를 찾을 수 있음. 그러면 순서가 흐릿해짐. Monus는 자연수 (ℕ, +, 0) 같은 양의 모노이드에서 자연스럽게 나타남.

이진 뺄셈 연산 은 다음처럼 파생됨:

x ∸ y = z   (y ≤ x이고 x = y • z인 경우)
       0    (그 외)

힙과의 연결 고리

힙 성질은 이것임: 모든 부모 노드의 가중치 ≤ 자식 노드의 가중치.

보통 구현은 각 노드에 절댓값(absolute weight) 을 저장함. 그런데 모노이드 구조가 있는 가중치를 쓸 때는 다른 관점이 가능함.

힙 성질 자체가 "자식 가중치 = 부모 가중치 + 어떤 차이" 를 보장하니까, 자식 노드엔 절댓값 대신 부모와의 차이(difference)만 저장해도 됨.

이 방식의 장점 두 가지:

  1. 타입 수준에서 힙 성질 위반 불가 — 잘못된 트리를 아예 표현할 수 없음
  2. 전체 가중치 일괄 변경이 O(1) — 루트 노드 하나만 바꾸면 트리 전체에 반영됨

Haskell 구현 — Pairing Heap with Monus

저자는 Monus 타입클래스를 정의하고 pairing heap을 구현함. Pairing heap을 선택한 이유는 구현이 단순하면서도 성능이 좋기 때문임 (포인터 기반 힙 중 실험적으로 가장 빠른 편).

핵심은 두 힙을 합칠 때 키 비교 후 자식의 키를 yk ∸ xk 로 저장하는 것. 절댓값이 아닌 부모와의 차이를 저장함으로써 힙 성질이 구조적으로 강제됨.

popMin에서도 이 구조가 빛남 — 루트를 꺼낼 때, 나머지 서브힙들 전체에 루트 가중치를 "더해줘야" 하는데, 차이만 저장하는 방식 덕분에 루트에만 더하면 됨 → O(1).

Max와 Last Monus — 일반 Ord 힙 복원

Monus가 없는 일반 키로도 쓰고 싶을 때는 MaxLast 세미그룹 래퍼로 해결함. 이 둘은 퇴화(degenerate)해 보이지만 Monus 법칙을 만족함. Last는 불필요한 비교를 줄여서 더 효율적임.

Phases — 자유 대수적 페어링 힙

Phases 어플리케이티브(Easterly 2019)는 효과(effect)들을 키 기준으로 재정렬하는 어플리케이티브 트랜스포머임. 원래는 정수 단계(integer phases)만 지원했는데, 저자는 이를 임의의 전순서 키로 일반화함.

구조는 pairing heap을 그대로 응용하되, 각 노드에 순수 값 대신 효과적 계산(effectful computation) 을 담음. 실행하면 키 순서대로 효과가 발생하고, 순수 값의 원래 구조는 보존됨.

안정적 정렬을 위한 Key Monus

문제: pairing heap 기반 정렬은 불안정(unstable) 함. 같은 키를 가진 효과들의 순서가 보장되지 않음 → 어플리케이티브 법칙 위반.

해결책: Key k = (k, Int) 형태의 새 Monus. 키가 같을 경우 원래 위치의 오프셋으로 타이브레이킹함.

중요한 점은 이 오프셋이 완전히 로컬 하다는 것 — 전체 시퀀스를 한 번 훑어서 인덱스를 붙이는 게 아니라, 각 노드가 자신과 이웃 간의 상대적 차이만 알면 됨. 차이를 저장하는 패턴이 여기서도 그대로 적용됨.

힙 크기를 추적해서 오프셋을 부여하고, 차이 저장 덕분에 O(1)로 전체 오프셋 조정 가능.

모나딕 힙 — Free Monad Transformer

저자의 2021년 논문에서 나온 Search 타입은 free monad transformer 기반의 모나딕 힙임. list 모나드와 (,) k 펑터(writer 모나드)를 레이어로 쌓은 구조.

이 타입에서는 대수적으로 차이를 저장해야 의미론적으로 올바름 — 레이어들을 join으로 합칠 때 가중치가 제대로 누적되려면 각 레이어가 절댓값이 아닌 차이를 담고 있어야 함.

결론 — 핵심 원리 하나

"절댓값을 저장하지 말고 차이를 저장하라."

이게 이 포스트 전체를 관통하는 주제임. Monus는 이 패턴을 대수적으로 형식화하는 도구. 가중치가 monus 구조를 가질 때 적용 가능하고, 적용하면:

  • 불변식(invariant)이 타입 수준에서 강제됨
  • 전역 조작이 O(1)로 줄어듦
  • 안정 정렬까지 같은 아이디어로 확장됨

그래프 탐색, 정렬, 효과 순서 제어 등 순서 기반 알고리즘 전반에 유용한 패턴임.

핵심 포인트

  • Monus는 순서가 모노이드 연산으로 정의되는 대수 구조로, x ≤ y ⟺ ∃z. y = x • z
  • 힙에서 절댓값 대신 부모와의 차이를 저장하면 힙 성질이 타입 수준에서 강제되고 전체 가중치 수정이 O(1)이 됨
  • Max/Last 세미그룹 래퍼로 일반 Ord 키를 monus 힙에서도 사용 가능
  • Phases 어플리케이티브를 pairing heap으로 구현하면 임의의 전순서 키로 효과를 재정렬할 수 있음
  • Key monus(키 + Int 오프셋)로 안정 정렬을 달성하며, 오프셋 정보가 완전히 로컬하게 유지됨

인사이트

같은 '차이를 저장한다'는 패턴이 힙 구조, 안정 정렬, 모나딕 레이어 합성까지 일관되게 관통함. 대수 구조가 자료구조 설계 원칙과 이렇게 자연스럽게 맞물리는 예시가 드문데, monus가 그 접점을 깔끔하게 포착함.

댓글

댓글

댓글을 불러오는 중...

backend

Redis 8.0 출시 — I/O 스레딩 갈아엎고 처리량 3배, 2.1M ops/sec 달성

Redis 8.0이 I/O 스레딩 모델을 완전히 재설계해서 16코어 기준 2.1M ops/sec를 달성함 (7.4 대비 3배). Hash field expiration, Vector search HNSW, Client-side caching v2, Redis Functions 2.0 async 실행 등 굵직한 기능이 추가되고, jemalloc 통합으로 메모리 fragmentation도 25% 줄어듦.

backend

Go 1.26의 타입 생성(Type Construction)과 순환 감지(Cycle Detection) 개선

Go 1.26에서 타입 체커의 타입 생성 알고리즘을 개선해 재귀 타입과 배열 크기 계산 시 발생하던 순환 감지 문제를 체계적으로 해결했다. 불완전한 값이 다운스트림으로 퍼지기 전에 업스트림에서 차단하는 새로운 접근법으로 여러 컴파일러 패닉을 수정.

backend

Cloudflare Gen 13 서버: 캐시를 코어로 바꿔 성능 2배 달성한 이야기

Cloudflare가 AMD Turin 9965(192코어) 기반 Gen 13 서버를 배포함. 코어당 L3 캐시가 6배 줄어 레거시 NGINX 스택(FL1)으로는 레이턴시 50% 악화가 불가피했으나, Rust로 전면 재작성한 FL2로 전환해 Gen 12 대비 처리량 2배, 성능/와트 50% 개선을 달성함.

backend

칩셋 레이턴시를 측정해봤더니 — 쓸모는 없지만 재밌는 실험

Vulkan GPU 벤치마크로 여러 세대 마더보드 칩셋의 PCIe 레이턴시를 측정한 실험. CPU 직결 대비 칩셋 경유 시 수백 ns 레이턴시가 추가되며, 의외로 2012년 Skylake Z170이 가장 낮은 추가 레이턴시를 보임.

backend

ForgeKV — Rust로 만든 멀티코어 Redis 대체제

Rust로 만든 Redis 드롭인 대체제. 64-샤드 잠금 아키텍처로 멀티코어 스케일링 지원. 2코어 환경에서 Redis 7 대비 41% 빠른 SET 처리량(158K ops/s). 고동시성에서는 약점 있음. Source-available 라이선스.