TigerBeetle의 'push ifs up, fors down' 관용구 정리한 블로그, HN에서 벤치마크 없다는 비판 받아
- TigerBeetle의 Tiger Style 문서가 switch/if는 부모 함수에 두고 분기 없는 로직 조각은 헬퍼 함수로 옮기라고 권고한 push ifs up and fors down 휴리스틱을 글이 정리했고, matklad도 2023년 11월 15일 블로그로 같은 주제를 다룬 바 있음
- if를 위로 올린다는 건 frobnicate(walrus: Option) 대신 호출자가 None을 걸러내고 Walrus만 넘기게 해서 함수 타입이 사전조건을 드러내게 하라는 뜻임
- for를 아래로 내린다는 건 frobnicate_batch(walruses)처럼 루프를 함수 안에 두어 hot loop에서 분기를 없애고 벡터화 후보로 만든다는 것임
- DB 최적화에서도 같은 원리라서 프로젝션과 셀렉션은 트리 아래로 밀어 일찍 실행하고 조인은 나중에 두며, Volcano식 튜플 단위 next() 호출 대신 벡터화 실행이 배치 1000개 단위로 연산자를 호출함
- 범주론에서는 술어 p: A -> Bool의 부분집합과 포함 사상 {a | p a} ↪ A가 subobject(monomorphism)를 이루는 대응으로 설명함
Hacker News 의견들
LLM이 사소한 아이디어에 온갖 비유 붙여서 장황한 블로그 글 만드는 솜씨에는 매번 감탄하게 됨
그건 인간의 전통적인 활동 영역을 또 하나 잠식한 거지
이 글에서 정작 빠진 게 벤치마크임. C# 9부터 닷넷 런타임이 안전하다고 판단되면 자동으로 해주고 gcc나 llvm도 마찬가지인데 왜 언급이 없냐
JVM에서는 이 최적화 깨기 쉬움. 루프 안에서 안 바뀌는 값이라도 해시맵 조회처럼 복잡하면 매번 get 호출해버림
C/C++는 앨리어싱 때문에 char*가 끼면 컴파일러가 이런 최적화를 아예 안 함
난 몇 년째 이렇게 짜왔는데 속도 때문이었던 적은 거의 없음. 코드가 읽기 쉬워질 때만 씀
난 이걸 '한 개짜리는 하지 마라'라고 표현해왔음. 배치가 원시 연산이라는 말이 딱 맞네
한 문장으로 요약하면 루프에 분기가 없어서 벡터화 후보가 된다는 거임. 그게 글의 전부임. 근데 그거 대규모 과학 계산에서는 진짜 큼
대신 I/O에 묶여서 데이터나 옮기는 99% 개발자한테는 이득이 작음
if문을 앞에 두는 건 그냥 가드 절 아닌가. Swift는 guard 문이 따로 있고 Rust도 let else가 비슷한데
가드 절은 예외적인 경우를 일찍 반환하는 거고, 갓 졸업한 사람들이 대칭성 깨진다고 싫어하는 패턴이지
난 오히려 반대라고 봄. 조건문을 깊이 넣어서 상위 흐름을 규칙적으로 만드는 게 낫다고 생각했는데, 데이터를 나누는 일과 결정하는 일을 한 곳에 섞지 않는 게 핵심인 듯
컴파일러가 for 안의 if를 밖으로 빼서 if 하나에 for 두 개로 만들어주기도 함
이 글 읽을 시간에 matklad 원문을 읽는 게 나음