성능 엔지니어링, 프로파일러, 그리고 보이지 않는 것을 보기
원문은 Nelson Elhage님이 에 게재했습니다. 이 블로그 구독하기
최근에 게리 클라인과 로버트 호프만이 쓴 논문 “Seeing the Invisible: Perceptual-Cognitive Aspects of Expertise”를 접하게 됐다. 훌륭한 논문이니 기회가 되면 꼭 읽어보길 권한다.
클라인과 호프만은 전문가가 “존재하지 않는 것을 보는” 능력에 대해 논한다. 환경에 존재하는 데이터와 단서를 관찰하는 것을 넘어, 전문가는 그 단서가 함의하는 바를 지각한다는 것이다. 예상되거나 “전형적인” 정보가 부재하다는 사실, 관찰된 데이터가 전형적인지 비전형적인지, 그리고 한 시점의 스냅샷이나 짧은 기간의 관찰을 바탕으로 시스템의 과거와 미래 시간 궤적이 어떠했을지 혹은 어떨지에 대한 가능성 등이 그 예다.
이 글에서는 그 논문의 몇 가지 아이디어를 성능 엔지니어링이라는 구체적인 맥락에서, 그리고 프로파일러가 해줄 수 있는 것과 해줄 수 없는 것이 무엇인지에 대해 이야기해보고자 한다. 특히 이 논문은 프로파일러가 왜 그토록 귀중한 도구이면서도 동시에 성능 엔지니어링의 전부일 수는 없는지를 논하는 데 훌륭한 렌즈가 된다고 생각한다.
프로파일러
나는 — 때로는 암묵적으로 — 소프트웨어를 더 빠르게 만드는 일이란 결국 다음 루프가 전부라는 말을 자주 접한다.
- 프로파일러를 실행한다
- 프로파일에서 가장 많은 시간을 차지하는 부분을 찾는다
- 그 부분을 더 빠르게 만든다
하지만 실제로 나나 다른 사람들이 이 루프를 말 그대로 따라 해보면, 매우 빠르게 수확 체감에 부딪히게 된다. 소프트웨어는 여전히 “당연히” 혹은 “가능할 것 같은” 수준보다 — 혹은 우리가 원하는 수준보다 — 훨씬 느린데, 그렇다고 어떻게 더 개선해야 할지는 명확하지 않다.
이런 괴리가 생기는 이유는 여러 가지가 있다고 보며, 이 현상만으로도 열두 편은 족히 글을 쓸 수 있겠지만, 위 에세이를 염두에 두고 다음 아이디어에 대해 이야기해보려 한다.
프로파일러는 “눈에 보이는 것”만 보여줄 수 있다. 능숙한 성능 엔지니어링은 프로파일을 만들고 의미를 부여하지만 정작 프로파일 자체에는 “보이지 않는” 정보를 이해하는 것이 필요하다.
보이지 않는 것을 보기
이게 무슨 뜻일까? 프로파일에 “있는” 정보와 “없는” 정보는 각각 무엇일까?
프로파일러가 보여주는 것은 “눈에 보이는 것”이다. 프로그램의 특정 실행이 어디서 시간을 쓰고 있는지를 대개 함수나 스택 프레임 단위로 매우 구체적이고 문자 그대로 보여준다. 물론 이는 분명 유용한 정보다.
하지만 이것만으로는 부족하다! 때로는 명백한 버그로 인한 명백한 핫스팟을 우연히 발견하기도 하지만, 훨씬 더 많은 경우 — 특히 프로그램이 어느 정도 최적화된 이후에는 — 어떤 조치를 취하고 어떤 최적화를 시도할지 결정하기 위해 추가 정보가 필요하다. 어디서 시간이 쓰이는지만 아는 것으로는 충분하지 않다. 어떤 시간 소모는 제거하거나 줄이는 것이 가능한지, 그리고 거기에 얼마나 많은 노력과 위험이 따르는지도 이해해야 한다.
구체적인 예를 들어보자. 프로그램이 실행 시간의 80%를 해시 테이블 조회에 쓰고 있다는 것을 알게 됐다고 하자. 그렇다면 내 해시 테이블을 최적화해야 할까, 아니면 조회 연산 자체를 줄이는 방향으로 설계를 바꿔야 할까? 프로파일 자체는 이를 직접 알려주지 않는다. 하지만 전문가는 자신의 전문성과 배경지식을 통합해 프로파일에서 이런 종류의 정보를 어쨌든 간파해낼 수 있다.
다른 관점에서 보면, 프로파일은 프로그램이 특정 연산에 얼마나 많은 시간을 썼는지는 알려주지만, 그 연산을 최적화한다면 얼마나 시간이 들지를 알려주지는 않는다.
어떤 연산이 느린 이유는 그것이 프로그램의 “진짜 작업”을 나타내며 쉽게 줄일 수 없는 본질적인 작업이기 때문일 수도 있고, 반대로 비본질적인 이유로 느려서 어쩌면 완전히 제거할 수도 있는 경우도 있다. 프로파일러는 프로그램이 그 연산에 시간을 쓰고 있다는 사실은 알려주지만, 그 연산이 이미 잘 최적화되어 있는지, 혹은 제거하거나 다른 곳으로 옮길 수 있는 작업인지는 알려주지 않는다. 하지만 이 역시 프로파일을 읽는 전문가는 그 질문들에 대한 답을 도출해낼 수 있다.
심지어 프로파일러가 제공하는 분석의 “프레임” 자체가 이런 질문을 던지기에 가장 유용한 프레임이 아닐 수도 있다. 프로파일러는 정보를 대체로 시간 순서대로, 혹은 함수나 스택 프레임별로 묶어서 정리하는 경향이 있지만, 다른 방식으로 정리하는 것이 훨씬 생산적인 경우가 많다. 예를 들면 다음과 같다.
때로는 소요 시간을 I/O 시간과 CPU 시간으로 나누어 분석하는 것이 더 중요하다. 프로그램이 두 작업을 번갈아 수행하거나 (혹은 다양한 비율로 병렬로 수행) 할 수 있는데, 스택 프레임에 깔끔하게 대응되지 않는 여러 범주의 I/O나 CPU 시간별로 시간을 세분화하고 싶을 때가 있다.
때로는 — 위 내용을 약간 일반화하자면 — 가장 유용한 축은 “프로그램이 어떤 하드웨어 자원을, 어떤 비율로 사용하고 있는가?”이다. 프로그램에 따라 이는 다양한 관점을 열어준다.
- 마이크로아키텍처 자원: CPU 실행 유닛, 캐시 공간, 디코더 용량 등
- GPU vs CPU vs PCI 혹은 NVLink 버스 대역폭
- 이더넷 대역폭 vs 로컬 디스크 대역폭 vs CPU 처리량
- 그 외에도 셀 수 없이 많다
때로는 물리적인 스택 트레이스와는 다른 축이나 조직화 프레임으로 정리해야 할 필요가 있다.
여기서 전형적인 예는 Python 프로그램을 C 레벨 프로파일러로 프로파일링하려는 경우다. 이 경우 실행 시간의 대부분이
_PyEval_EvalFrame에서 소모된다는 결과를 얻게 될 것이다. 틀린 말은 아니지만 별로 유용하지는 않다. 유용한 이해를 얻으려면 프로파일을 Python 레벨 스택 트레이스라는 도메인으로 “끌어올려야” 한다. (이는 “다른 코드를 다루는 코드” 형태의 일부 자동화 도구가 공유하는 문제이기도 한데, 과거에 잠시 다룬 적이 있다.)
전문성과의 상호작용
클라인의 전문성에 관한 에세이는 이 상황을 이해하는 데 유용한 프레임을 제공한다고 생각한다.
전문가, 특히 특정 애플리케이션이나 프로그래밍 언어 혹은 프레임워크에 대한 도메인 전문가는 프로파일을 보고 위 질문들 중 많은 것에 대한 답을 프로파일에 문자 그대로 존재하지 않음에도 “보는” 경우가 많다.
그들은 프로파일러가 문자 그대로 답해주는 질문에만 머물지 않고 “행간을 읽어” “올바른” 혹은 “더 나은” 질문을 던지고 심지어 답하기도 한다. 더불어 중요한 점은, 어떤 이유로든 그렇게 할 수 없을 때 전문가들은 프로파일러가 잘못된 질문을 던지고 있다는 사실을 훨씬 더 빨리 인지하고, 담고 있지도 않은 이해를 어떻게든 얻어내리라 기대하며 같은 프로파일만 붙잡고 머리를 싸매는 대신 다른 도구를 찾거나 만들어낸다는 것이다.
이러한 관점은 성능 엔지니어링에 대한 담론과 일반적인 이해에서 내가 느끼는 괴리 중 일부를 설명해주기도 한다고 생각한다. 숙련된 성능 엔지니어를 보면 실제로 프로파일을 들여다보는 데 많은 시간을 쓰는 것을 관찰할 수 있다. 이를 보고 어떻게 보면 그들이 하는 주된 일이 바로 그것이라고 결론짓기 쉽다.
하지만 자세히 들여다보면, 이들 숙련된 성능 엔지니어는 초보자나 순진한 관찰자가 예상하는 것과는 다른 방식으로 프로파일러를 사용하고 있다. 그리고 그 사용법은 프로파일에서 다른 정보를 볼 수 있는 능력에 의해 추동된다! 그들이 프로파일을 볼 때, 단순히 화면에 표시된 문자 그대로의 정보만 보는 것이 아니라, 그것을 여러 입력 중 하나로 활용해 프로그램에 대한 모델을 강화하고 구축하며, 시스템에 대한 멘탈 모델을 업데이트하고 발전시키고, 추가적인 최적화와 추가적인 프로파일링 실행(잠재적으로는 다른 프로파일러를 사용한)을 통해 검증할 가설을 세우고 시험한다.
글을 무작위로 읽기
댓글
로그인하고 댓글 남기기