Data Dependency Chain 이해하기 — CPU 루프 최적화의 핵심 원리
📺 원본 영상: Lightning Talk: Understanding Data Dependency Chains - Makar Kuznietsov - CppCon 2025 🎙️ 채널: CppCon 📅 정리 일자: 2026-05-20 ⚠️ Note: 본 리포트는 영상의 음성을 텍스트로 변환(STT)하여 정리한 것입니다. 화면에 표시된 자료(어셈블리 코드, 데이터플로우 그래프, 슬라이드 등)는 포함되지 않을 수 있습니다.
개요
이 라이트닝 토크는 CppCon 2025에서 Function.AI의 공동창업자 Makar Kuznietsov가 발표한 약 5분 분량의 집중 강연이다. 강연은 CPU가 명령어를 실행하는 방식, 특히 타이트 루프(tight loop) 내부에서 데이터 의존성이 성능을 어떻게 결정하는지를 다룬다. 핵심 메시지는 하나다. 루프를 최적화하려면 먼저 그 안에 존재하는 **주요 의존성 체인(Primary Dependency Chain)**과 **보조 의존성 체인(Secondary Dependency Chain)**을 구별해야 하며, 각각에는 서로 다른 최적화 전략이 적용된다는 것이다.
CPU 실행 모델과 데이터플로우 그래프
강연자는 먼저 매우 단순화된 CPU 모델을 전제로 논의를 시작한다. 배열 전체가 L1 캐시에 올라가 있고, 메모리 로드(load)에는 4사이클, 덧셈(add)과 비교(compare) 연산에는 각 1사이클이 소요된다고 가정한다. 이 모델은 실제 현대 CPU의 근사치로, 논의를 단순하게 유지하면서도 핵심 개념을 전달하기에 충분하다.
이 전제 위에서 강연자는 CPU가 명령어를 발행(issue)하는 방식을 **데이터플로우 그래프(Dataflow Graph)**로 시각화한다. 이 그래프는 하나의 코어에서 명령어들이 어떤 의존 관계를 가지고 순차적으로 또는 병렬로 발행되는지를 보여준다.
💡 [영상 참조] 강연자는 [00:57] 시점부터 화면의 데이터플로우 그래프를 참조하며 명령어 발행 순서와 의존 관계를 설명합니다. 상세 내용은 원본 영상을 확인하세요.
예제 1 — 단순 배열 합산에서 드러나는 의존성 체인
첫 번째 예제는 배열의 원소를 순서대로 더하는 가장 단순한 루프다. 강연자는 이 루프의 어셈블리 표현을 Godbolt(Compiler Explorer)로 확인하며 설명을 전개한다.
💡 [영상 참조] 강연자는 [00:35] 시점에서 화면 오른쪽의 어셈블리 코드와 Godbolt 링크를 참조합니다. 상세 내용은 원본 영상을 확인하세요.
데이터플로우 그래프를 분석하면 명령어들이 두 개의 뚜렷한 흐름으로 나뉜다. 강연자는 이를 오렌지색으로 강조하며 설명하는데, 오렌지색으로 표시된 명령어들은 모두 포인터 변수에 의존하는 것들이다. 포인터를 전진시키는 명령어가 완료되기 전까지는 다음 명령어를 발행할 수 없으므로, 이것이 루프 전체의 처리 속도를 결정하는 **주요 의존성 체인(Primary Dependency Chain)**이 된다. 포인터 전진 연산의 레이턴시(latency)는 1사이클이기 때문에, 이 루프의 처리량(throughput)은 사이클당 합산 1회로 결정된다.
💡 [영상 참조] 강연자는 [01:34] 시점에서 오렌지색으로 표시된 포인터 의존 명령어들을 가리키며 Primary Chain 개념을 설명합니다. 상세 내용은 원본 영상을 확인하세요.
반면, sum += num이라는 실제 누산(accumulation) 연산과 num을 메모리에서 불러오는 로드 연산은 **보조 의존성 체인(Secondary Dependency Chain)**을 구성한다. 이 체인은 포인터 체인에 의존하지만, 루프의 처리 속도 자체를 결정하지는 않는다.
여기서 강연자가 강조하는 핵심 직관이 등장한다. 만약 로드 레이턴시가 4사이클이 아니라 24사이클이었다면 어떻게 될까? 첫 번째 로드가 완료되는 시점이 20사이클만큼 늦어지고, 따라서 첫 번째 합산도 그만큼 지연될 것이다. 그러나 전체 루프 처리량은 6배 느려지지 않는다. 루프의 속도는 어디까지나 포인터 전진, 즉 Primary Chain의 레이턴시에 의해 결정되기 때문이다. 이 직관에 반하는 결론은 중요한 실용적 함의를 가진다. 이 루프 구조에서 로드 레이턴시를 줄이기 위한 최적화는 의미가 없다는 뜻이다.
예제 2 — 합산 분할을 통한 Secondary Chain 최적화
두 번째 예제에서 강연자는 루프 속도를 높이기 위해 합산을 두 개의 부분합으로 분리하는 방식을 보여준다. sum_even과 sum_odd, 즉 짝수 인덱스와 홀수 인덱스를 별도로 누산하는 구조다.
이 변경이 데이터플로우 그래프에 어떤 영향을 미치는지를 살펴보면 변화가 극명하게 드러난다. 포인터는 이제 매 사이클 2개의 정수(8바이트)씩 전진할 수 있다. 동시에 CPU가 가진 두 개의 로드 포트(load port)를 같은 사이클에 모두 활용할 수 있게 되어, 사이클당 2개의 로드를 병렬로 발행하는 것이 가능해진다.
💡 [영상 참조] 강연자는 [03:19] 시점에서 두 개의 부분합을 보여주는 데이터플로우 그래프를 참조하며 병렬 로드의 효과를 설명합니다. 상세 내용은 원본 영상을 확인하세요.
여기서 중요한 개념 전환이 일어난다. Secondary Chain에서는 레이턴시가 아니라 **처리량(throughput)**이 성능을 결정한다. 처리량이란 한 사이클에 병렬로 발행할 수 있는 명령어의 수를 뜻한다. 이 예제에서 CPU의 로드 포트가 최대 2개이므로, Secondary Chain의 처리량은 사이클당 2로 제한된다. 결과적으로 포인터 전진이 여전히 Primary Chain으로서 루프 성능을 지배하지만, 그 성능 자체가 이제 사이클당 8바이트, 즉 예제 1의 두 배가 된다. Secondary Chain이 이를 병목 없이 따라올 수 있기 때문이다.
핵심 결론 — Primary는 레이턴시, Secondary는 처리량
강연자가 스스로 “이 강연 전체를 건너뛰어도 이것만은 기억하라”고 말한 결론은 다음과 같다.
데이터 의존성 체인을 가진 타이트 루프를 최적화할 때는 반드시 두 종류의 체인을 먼저 식별해야 한다. Primary Dependency Chain에 대해서는 각 명령어의 레이턴시를 살펴야 한다. 이 체인의 레이턴시가 루프 전체의 반복 속도(iteration rate)를 직접적으로 결정하기 때문이다. 반면 Secondary Dependency Chain에 대해서는 **처리량(throughput)**을 최적화해야 한다. Secondary Chain은 Primary Chain과 상호작용하며 서로를 지탱하는 구조이기 때문에, 이 둘을 혼동하면 실질적인 병목이 아닌 곳에 최적화 노력을 낭비하게 된다.
이 원칙은 겉보기에는 단순하지만, “로드가 느리니까 로드를 줄이면 빠르겠지”라는 직관적이지만 틀린 추론을 막아주는 강력한 분석 프레임워크다. CPU 마이크로아키텍처 수준의 성능 분석에서 가장 먼저 적용해야 할 사고방식이라고 할 수 있다.
강연자 및 소속
Makar Kuznietsov는 Function.AI의 공동창업자이다. Function.AI는 언어 모델(LLM)을 활용하여 소프트웨어 성능 최적화를 자동화하는 에이전트를 개발하는 회사다. 강연자는 이 주제, 즉 루프 성능 최적화가 일상적인 업무라면 직접 대화하거나 웹사이트를 방문해 달라고 청중에게 안내했다.
📚 참고 자료 (강연 내용과 별개)
강연에서 언급된 도구와 개념을 더 깊이 이해하고 싶다면 아래 자료가 도움이 될 수 있다. 이 자료들은 강연자가 직접 추천한 것이 아니라, 강연 내용을 보완하기 위해 편집자가 제안하는 것이다.
Godbolt (Compiler Explorer): 강연자가 어셈블리 확인에 사용한 도구로, C++ 코드를 실시간으로 다양한 컴파일러와 최적화 옵션으로 컴파일하여 어셈블리를 확인할 수 있는 웹 서비스다. 데이터 의존성 체인을 직접 눈으로 확인하는 데 매우 유용하다.
Agner Fog의 최적화 매뉴얼: CPU 마이크로아키텍처, 명령어 레이턴시, 처리량 테이블 등 이 강연에서 다룬 개념들을 훨씬 깊이 있게 다루는 문서다. x86/x64 계열 CPU 최적화의 사실상 표준 참고서로 알려져 있다.
LLVM MCA (Machine Code Analyzer): 코드 블록의 처리량과 레이턴시를 정적으로 분석해주는 도구로, Primary/Secondary 체인 구조를 직접 분석하는 데 활용할 수 있다.
마무리
이 5분짜리 라이트닝 토크가 전달하는 메시지는 밀도가 높다. CPU 성능 최적화는 종종 “더 빠른 명령어를 쓰면 된다”는 단순한 직관으로 접근되지만, 실제로는 명령어들 사이의 의존 관계가 만들어내는 체인 구조가 전체 성능을 지배한다. 어디에 병목이 있는지를 올바르게 진단하지 못하면, 로드 레이턴시처럼 실제 성능에 영향을 주지 않는 부분에 최적화 노력을 쏟는 실수를 범하게 된다. Primary Chain은 레이턴시로, Secondary Chain은 처리량으로 분석하라는 이 강연의 결론은, 타이트 루프 최적화를 시작하는 모든 C++ 개발자가 노트에 적어둘 만한 원칙이다.
📚 출처 및 참고
| 구분 | 내용 |
|---|---|
| 원본 영상 | Lightning Talk: Understanding Data Dependency Chains - Makar Kuznietsov - CppCon 2025 |
| 채널 | CppCon |
| 강연자 | Makar Kuznietsov (Function.AI 공동창업자) |
| 리포트 작성 | 2026-05-20 |
| 작성 방식 | YouTube 영상 STT 변환 후 에디팅 |
💬 본 리포트는 영상 내용을 바탕으로 재구성되었습니다. 정확한 내용은 원본 영상을 참조해 주세요.
검수 노트
- STT 수정 1곳: “루프를 속도를 높이기 위해” → “루프 속도를 높이기 위해” (예제 2 섹션 첫 문장, 조사 중복 제거)
- [?] 항목: 없음
- 스크린샷 삽입: 목록 내 항목 5건 모두 로컬 파일 경로(
./eMnz158Y0Ok_sc_*.jpg)로, webUrl에 해당하는 항목이 없어 삽입 대상 없음