바보야, 문제는 메모리 지연 시간이에요

It's the memory latency, stupid

pivotlake.io ▲ 246 댓글 85 ntur1337

요약

CPU 사용률이 100%여도 실제로는 메모리를 기다리며 멈춰 있는 시간이 많습니다.

쿼리 엔진 Pivot을 만드는 엡시오 랩스의 마오르 케른이 쓴 글입니다. 약 9,900만 행을 사용자별로 묶는 GROUP BY(같은 값끼리 묶어 집계하는 SQL 문법) 쿼리를 하드웨어 카운터로 재서 시간이 어디로 가는지 추적했습니다.

왜 중요한가

  • CPU 사용률만 봐서는 코어가 계산 중인지 메모리를 기다리는 중인지 구분할 수 없습니다.
  • 실행하는 명령이 늘어도 메모리 대기가 줄면 쿼리가 더 빨라질 수 있음을 수치로 보여 줍니다.

핵심 내용

  • 단순 해시 테이블로는 3.9초가 걸렸고, 시간의 50.1%를 메모리 대기로 보냈습니다.
  • 라딕스 파티셔닝(해시값 상위 비트로 데이터를 나누는 기법)은 테이블을 작게 쪼개 CPU 캐시에 들어가게 합니다.
  • 이 방식으로 명령은 약 114억 개에서 193억 개로 늘었지만, 시간은 3.3초로 줄었습니다.
  • 메모리 대기 비율은 23.5%로 떨어졌고, DRAM에서 읽은 양은 4.5GB에서 1.5GB로 줄었습니다.
  • 세부 구현을 밝히지 않은 최종 버전은 단일 코어 1.8초, 16코어 172ms를 기록했습니다.

HN 반응

  • 캐시를 고려해 자료 구조를 바꾸려다 벤치마크를 내밀어도 "섣부른 최적화"라는 반대에 부딪혔다는 경험담이 이어졌습니다.
  • 이런 감각을 갖춘 개발자가 줄고 있다는 한탄이 나왔고, 범용 게임 엔진이면 충분한지를 두고도 의견이 갈렸습니다.

댓글

30개 표시 · 전체 85개
  1. boricj HN

    저는 회사에서 다른 팀과 함께 일하는데, 그 팀은 메시지 패싱을 잔뜩 쓰고, 깊게 중첩된 클래스 계층에 C++ 가상 메서드를 쓰고, 커다란 DOM에 기성 C++ STL 컨테이너까지 씁니다. 그걸 인오더(in-order) 코어인 Cortex-A7에 캐시도 쥐꼬리만 한 STM32MP1에서 돌립니다.

    반면 저희 팀에서는 제가 인라인으로 할당하는 STL 컨테이너를 직접 만들었고, SAX 방식의 처리 라이브러리와, 2000년대 비디오 게임처럼 인덱스와 세대(generation)로 링크를 거는 오브젝트 풀을 씁니다. 램이 1MB도 안 되는 STM32H5에서요.

    기계와의 교감(mechanical sympathy)은 보여 주는 사람마다 이미 잊힌 기술처럼 받아들입니다. 첫 번째 팀에는 벤치마크 수치까지 들이밀면서, 우리 사이에서 병목이 되는 쪽은 제가 아니라는 것, 그것도 몇 자릿수 차이로 아니라는 것을 증명해야 했습니다.

  2. bunderbunder HN

    저도 코드 리뷰에서 데이터 구조를 캐시 친화적으로 재구성하는 데 반대를 겪었습니다. 이유는 "섣부른 최적화"라는 것이었죠. 저는 성능 차이가 크고 비즈니스에도 의미가 있다는 벤치마크 결과를 미리 내놓아 정당화했고, 테스트 중에 실제로 확인한 성능 병목을 해결하는 작업이었는데도요.

    클린 코드가 한창 유행하던 때의 팀이었습니다. 혹시 2010년대에 어떤 미학적 기준이 객관적이고 증명 가능한 정답이며 다른 무엇보다 중요하다고 믿어 버린 사람이 많아진 게 문제가 아니었나 싶기도 합니다.

  3. hinkley HN

    개발자들의 의견에 대한 제 낮은 평가는 상당 부분 프로젝트 두어 개에서 비롯됐습니다. 제품팀과 마케팅팀이 큰 폭의 성능 개선을 애타게 요구하는데, 개발팀은 이게 기대할 수 있는 최고 속도라고 버티던 프로젝트들이었어요. 저는 두 프로젝트 모두 처음 6개월 만에 2배를 끌어냈고, 그중 하나에서는 거의 2년 동안 분기마다 25%씩 개선했습니다(계산하면 대략 8배). 그러고 나서야 바닥이 보이기 시작했죠.

    "섣부른 최적화"는 대부분 생각을 막아 버리는 수단이고, 그래서 그렇게 자주 잘못 인용됩니다. 크누스의 원래 말은 더 섬세합니다. 그들의 용법에는 그런 섬세함이 전혀 없고요.

    타협책으로 가독성도 함께 좋아지는 최적화를 정말 잘하게 됐습니다. 호이스팅과 더 나은 변수 이름 짓기를 같이 하면 그냥 코드 정리로 착각하기 쉽거든요.

    또 키홀(keyhole) 최적화가 더 받아들여지기 쉽다는 것도 알게 됐습니다. 느린 부분을 책임 하나만 지는 별도 함수로 옮기고, 최적화는 별도 커밋으로 하는 겁니다. 그러면 다른 개발자들은 제 변경을 들여다볼 필요가 없고, 버그가 나오면 나중에 언제든 되돌릴 수 있습니다. 버그가 나오는 일은 거의 없지만, 되돌릴 수 있다는 선택지가 있다는 것만으로 일이 한결 매끄러워지는 듯합니다.

  4. nomel HN

    제 경험칙은 이렇습니다. 쉽게 2배가 나오는 게 보이면, 공을 들였을 때 10배쯤은 실제로 나옵니다. 쉬운 2배가 있다는 건 아무도 성능에 시간을 쓴 적이 없다는 뜻이니까요.

  5. vrighter HN

    저는 이런 식으로 800배 넘게 얻었습니다. 곳곳에 O(n^2)에 n+1 쿼리였거든요.

  6. hinkley HN

    아주 오래전에 TogetherJ에 버그를 제보한 적이 있습니다. 모노레포와 분리 컴파일 단위에서 버그가 있어서, 여러 프로젝트 파일을 한꺼번에 불러올 때 시간 복잡도가 n^5이 된다는 걸 실측으로 밝혀냈거든요. 마지막 측정값은 어느 날 오후 5시에 로딩을 시작했더니 이튿날 오전 11시에 끝난 것이었습니다. 그쪽에서 우회책을 줘서 20분쯤으로 줄었고, 패치가 나오면서 그보다 조금 나아지긴 했지만 그래도 긴 시간이었습니다.

    팩토리얼 복잡도 코드도, 적어도 설계 단계에서는 본 적이 있는 것 같은데, 실제 현장에서 만난 것 중에는 이게 최악이었습니다.

  7. andyferris HN

    극단까지 갈 수도 있습니다. 동적 언어에서 정적 언어로 옮기는 것만으로 100배, 자료구조와 알고리즘을 손봐서 또 100배가 나오기도 하고요.

    한번은 회사의 병목이라고 소개받은 문제에서 정확히 이런 1만 배 속도 향상을 보여 줬는데 그냥 무시당해서 몹시 당황한 적이 있습니다(제가 그 팀 소속은 아니었지만, 그래도 그쪽 사고방식과 인센티브는 이해하기 어렵습니다).

  8. hyperman1 HN

    90년대에 어떤 학생이 여름 아르바이트로 대기업의 배치 코드를 빠르게 만들게 된 이야기를 들은 적이 있습니다. 원래 코드는 버블 정렬까지 포함해 거의 모든 걸 잘못 만들어 놨다고 해요. 학생이 성실하게 엉망을 정리했고, 몇 시간 걸리던 작업이 몇 분으로 줄었습니다.

    그런데 그 뒤로 어디서도 면접을 보지 못했습니다. 뒷얘기를 들어 보니, 고용주들이 다들 그가 거짓말을 한다고 생각해서 그 경력을 이력서에서 빼야 했다고 합니다.

  9. kevsamuel HN

    솔직히 저라도 그렇게 생각했을 겁니다. 오컴의 면도날이죠. 거짓말하는 지원자가 넘쳐나는 판이니 다음 지원자도 거짓말일 가능성이 높습니다.

  10. jonathanlydall HN

    완전히 성능에 무신경한 것처럼 들리네요. 섣부른 최적화의 정반대 극단 같습니다.

    제 경험칙은 코드를 딱히 복잡하게 꼬지 않는 한 성능을 염두에 두고 쓰자는 것입니다.

    저는 C#이 전문인데, 때로는 일반 리스트 대신 해시 셋이나 딕셔너리를 쓰는 정도로 간단합니다.

    어떨 때는 EF(ORM)가 실제로 어떻게 동작하는지, 그리고 네트워크 호스트(DB 포함) 왕복 비용이 얼마인지를 제대로 이해하는 것이고요. (C#에서는 정적 타입 덕분에 ORM을 적절히 쓰면 정말 가치가 큽니다.)

  11. hinkley HN

    "섣부른 최적화"는 '나 생각하게 만들지 마(Don't Make Me Think)' 파의 구호가 됐습니다.

    저와 맞서던 사람들을 떠올려 보면, 그 무리는 리팩터링도 싫어했습니다. 코드를 외우고 있던 게 소용없어지니까요. 하지만 세월이 흐르고 보니, 코드를 고정해 두려는 것보다 코드를 찾아보기 쉽게 만드는 쪽이 훨씬 쓸모 있었습니다. 어차피 동료들은 새 기능을 구현하려고 이것저것 바꿔야 하니까요. 그 기능이 정확성이나 성능과 무관해도 마찬가지고요. 피해야 할 건 사람들한테서 로직을 숨기는 일입니다. 로직을 함수 호출로 옮기는 건 같은 동네 안이니 괜찮습니다. 하지만 호출 트리에서 훨씬 앞쪽으로 끌어올려 버렸다면, 회의에서 알리고 가능하면 문서에도 적어서 사람들이 상황을 알게 해야 합니다.

  12. vrighter HN

    해시 셋을 쓰긴 썼더라고요... 그러고는 ".Where(x=>x == key).Count() == 0"을 쓰더군요.

  13. rowanG077 HN

    사소한 지적이지만, 8분기 동안 25%씩 개선하면 1.25^8이라서 실행 시간이 거의 1/6로 줄어드는 셈입니다.

  14. hinkley HN

    25% 빠르다는 건 같은 시간에 하는 일이 1.33배라는 뜻입니다. 구하려는 건 0.75^7(거의 2년)인데, 그러면 원래 시간의 13.3%, 즉 1/7.5이 됩니다. 다만 실제로는 25~30% 정도였어서 반올림했습니다.

  15. kaashif HN

    사람들이 '빠르다'는 말을 이렇게 다르게 받아들이는 게 흥미롭네요.

    당신에게 2배 빠르다는 같은 시간에 2배의 일을 한다는 뜻이고, 25% 빠르다는 같은 시간에 1.33배의 일을 한다는 뜻이군요.

    소프트웨어에서는 50% 빠르다를 소요 시간 50% 감소로 받아들이는 경우를 자주 봅니다.

    자동차가 50% 빠르다면 시속 20마일에서 30마일이 된다고 거의 모두가 이해할 텐데, 1시간 걸리는 길이 50% 빠르면 얼마나 걸리느냐고 물으면 30분이라고 답하는 사람이 많을 겁니다. 그렇게 말하는 게 그럴듯해 보이니까요.

  16. hinkley HN

    성능은 사용자를 위한 겁니다. 사용자가 이 작업을 10초 안에 끝내야 하는 SLA가 있다고 했는데 현재 30초가 걸리고, 첫 번째 변경으로 15초가 됐다면 사용자에게 어떤 개선이라고 보고하겠습니까?

    시간 기준으로 보고하겠죠. 그러면 시간이 절반이니 50%입니다.

    예전에 쓰던 벤치마크 도구 하나는 초당 5만 연산에서 20만 연산이 된 걸 75% 빨라졌다고 보고했습니다. 분명히 틀렸지만 어떻게 그렇게 나왔는지는 압니다. 이 도구는 초당 연산 수를 세는 게 아니라 루프를 몇천 번 돌려서 몇 밀리초 걸리는지 재거든요. 그런 다음 그걸 초당 연산 수로 변환하는 겁니다. 변경 전후를 시간 기준으로만 비교하면 75%가 맞지만, 시간 기준 비율 바로 옆에 처리량을 같이 보고하면 곤란해집니다.

    그 도구의 유지보수가 끊긴 뒤 후임자는 현명하게도 배수로 보고하는 방식으로 바꿨습니다. 4배 빠르다는 오해하기 어렵죠. 그래도 "1.1배 느리다"는 여전히 오해의 여지가 있지만요.

  17. smolder HN

    50% 빠르다는 시간이 2/3가 된다는 뜻입니다. 다만 유명한 사람들도 이 실수를 했고, 심지어 공개적으로 끝까지 우기기까지 했죠.

  18. hinkley HN

    시간 제한을 맞추라는 요구는 속도(rate)가 아니라 시간 기준이니까, 개선도 시간 기준으로 보고하게 됩니다.

    요즘은 더 흔한 지표인 리틀의 법칙(Little's Law)도 시간 기준으로 보면 됩니다. 서버 처리량을 2배로 늘리면 클러스터 크기를 절반으로 줄여도 같은 수준의 서비스를 유지할 수 있습니다. 그러니 200%가 아니라 50%인 거죠.

  19. plasticchris HN

    그냥 패턴 매칭일 뿐입니다. 대부분은 그 정도로 준비하지 않으니, 당신도 그랬으리라고 짐작하는 거예요. 보통은 악의가 없고, 짚어 주면 다들 조금 민망해하겠지만, 요령 있게 말하면 괜찮습니다.

  20. smolder HN

    예산이 아주 큰 자바 프로젝트에서 저도 비슷한 일을 겪었습니다. 메모리 압박이 심하고 성능이 나쁜 코드는 만들고 싶지 않다고 했더니, 선임이 그건 섣부른 최적화라고 하더군요. 결국 우리 해법을 채택하지 않아서 상관없게 됐습니다. (그쪽은 메인프레임을 계속 썼는데, 그럴 만하다고 생각합니다.)

  21. jordand HN

    저는 게임 업계에서 일하는데, 그런 걸 할 수 있는 뛰어난 시스템 프로그래머들이 있습니다. 그분들이 나이를 먹어 가는데, 젊은 세대에는 그쪽에 관심 있는 프로그래머가 점점 줄고 있어요. 안타깝게도 기술이 갈수록 언리얼 엔진 5로 획일화되고 있습니다.

  22. MisterTea HN

    Technology is sadly increasingly homogenising around Unreal Engine 5 (안타깝게도 기술이 갈수록 언리얼 엔진 5로 획일화되고 있습니다)

    게임 엔진과 언리얼이 상품화되는 건 어느 정도 이해가 갑니다. 게임 회사들이 그래픽과 상호작용성에서 서로 앞서려고 경쟁하며 게임 혁명이 펼쳐지는 걸 우리 중 상당수가 지켜봤을 거예요. 하지만 UE5, 유니티, 고도 정도의 수준에 이르면 게임 엔진을 처음부터 만들 필요나 가치는 별로 없습니다.

    오해하진 마세요. 엔진을 쓰고 싶다면 얼마든지 쓰세요. 하지만 그저 게임을 만들고 싶은 거라면 기존 엔진으로 충분합니다.

  23. mananaysiempre HN

    오블리비언 리마스터가 출시됐을 때 게이머들 사이에서 흔히 나온 말이 "하드웨어만 좋으면 훌륭하지만, 언리얼이니까 당연히 거지같이 돌아간다"였습니다. 그러니 고객 정서가 당신 의견에 동의한다고 보기는 어렵습니다. 저도 데시마(Decima)가 할 수 있는 걸 본 뒤로는 언리얼이 게임 기술의 정점이라는 데 동의하지 않게 됐습니다.

  24. Jtarii HN

    다들 게임 엔진을 안 만들면 언리얼 엔진, 유니티, 고도는 누가 프로그래밍하나요?

  25. bunderbunder HN

    저도 한참 전부터 흐름을 놓쳤다는 건 인정하지만, 게임 엔진과 그래픽 카드의 쳇바퀴가 사실 게임 엔진과 그래픽 카드를 파는 사람들의 주머니를 채우려는 게 주된 목적이 아닐까 하는 생각이 한편으로 듭니다. 플레이어로서 기술과 즐거움 사이에 연결이 있다고 느끼는 제 주관적인 감각은 10년쯤 전부터 약해지기 시작했습니다.

  26. Jtarii HN

    2010년대에 스튜디오들이 누가 가장 크고 야심 찬 게임 세계를 만드는지 서로 경쟁했던 탓이라고 봅니다. 그 결과 개발비가 부풀었고, 제작비가 오르면 더 안전한 설계 결정을 택하는 피드백 루프가 생겼습니다.

    게임이 "영화 같아야" 한다는 점이 강하게 강조된 것도 있는데, 그러려면 사실적인 렌더링이 필요하거든요.

    AAA 게임은 몇몇 예외를 빼면 한동안 꽤 예술성이 없었습니다.

  27. bunderbunder HN

    저를 특히 질리게 한 건 그 "영화 같다"는 부분입니다. 기술적으로는 인상적이지만, 폴리곤은 적어도 영감은 더 많은 저예산 게임들이 보여 준 것만큼 시각적으로 흥미롭지는 않아요.

  28. alpaca128 HN

    계획된 결과라기보다는 이윤 극대화에 따른 편리한 결과라고 봅니다. 스튜디오는 최적화를 안 하는 게 더 싸고, 엔비디아는 DLSS 기능이 덩달아 유명해지는 걸 마다하지 않으니까요.

    크라이시스 3는 10년도 더 된 게임인데 지금도 멋지게 보이고, 요즘은 웹 브라우징에도 모자랄 사양에서 돌아갔습니다. 그 뒤로 게임에서 시스템 요구 사양을 크게 올릴 만한 걸 본 적이 없습니다.

  29. ses1984 HN

    크라이시스 3를 돌릴 수 있던 하드웨어에서 웹 브라우징이 부족하다니, 어떤 점이 부족하다는 건가요?

  30. lelanthran HN

    글쎄요, 제 9600GT에서 돌아갔습니다. 2016년에 쓰던 데스크톱에 달려 있던 건데, 최근 몇 주 동안 램 16GB짜리 그 데스크톱을 다시 썼거든요.

    WebGL을 쓰는 사이트는 하나도 볼 수 없었습니다. 게임 사이트 거의 전부와 차트·그래프 앱 상당수가 해당됩니다.

Hacker News에서 보기 ↗