
스타트업에서 '야생형 개발자'로 구르던 시절, 성능 문제의 범인은 언제나 정해져 있었습니다.
"이거 DB 문제네."
"네트워크가 느려서 그래."
"디스크 I/O가 병목이야."
우리는 늘 I/O(Input/Output)를 탓했습니다. CPU는 번개처럼 빠른데, 데이터를 가져오는 디스크가 거북이처럼 느리다고 믿었으니까요.
최근 네임드 기업으로 이직해서 레거시 시스템을 뜯어보다가, 문득 2022년에 나온 흥미로운 벤치마킹 결과 하나를 보고 뒤통수를 세게 맞았습니다.
"더 이상 I/O가 병목이 아니다."
이 말이 그냥 하는 소리가 아니더군요.
오늘은 제가 겪은 충격적인 '속도의 진실'과, 우리가 왜 CPU를 의심해야 하는지에 대해 털어놓으려 합니다.
1. 우리의 상식은 10년 전에 멈춰있다
개발자 면접 단골 질문인 '단어 빈도 세기(Word Count)'를 예로 들어보죠.
대용량 텍스트 파일을 읽어서 단어 개수를 세는 작업입니다.
보통은 디스크에서 읽어오는 속도가 느리니, 버퍼링을 잘하라는 식의 답변을 합니다.
그런데 벤치마킹 결과를 보니 상황이 달라졌습니다.
최신 NVMe SSD 환경에서 순차 읽기(Sequential Read) 속도는 상상을 초월합니다.
웜 캐시(Warm Cache) 기준으로 무려 12.8 GB/s가 나옵니다.
1초에 12기가바이트를 퍼올린다는 소리입니다.
2. 문제는 디스크가 아니라 '내 코드'였다
그럼 디스크가 12.8 GB/s로 데이터를 던져주면, 우리 코드는 그걸 다 받아먹을 수 있을까요?
저도 처음엔 "당연하지, C언어로 짜면 날아다닐 텐데"라고 생각했습니다.
그런데 원본 글의 저자가 GCC -O3 옵션(최적화)을 켜고 C로 짠 코드를 돌려봤더니 결과가 참담했습니다.
처리 속도: 278 MB/s.
기가바이트가 아니라 메가바이트입니다.
디스크는 12GB를 줄 준비가 되어 있는데, CPU가 고작 0.2GB 정도밖에 처리를 못 하고 있는 겁니다.
병목은 디스크가 아니었습니다.
범인은 바로 CPU, 그리고 그 CPU를 제대로 못 쓰는 '코드'였습니다.
3. 왜 CPU가 범인인가? (분기 예측의 배신)
도대체 왜 이렇게 느릴까요?
범인은 코드 속에 숨어있는 if 문들입니다.
단어를 세려면 공백인지, 문자인지 확인해야 하죠.
if (c == ' ')
if (c == '\n')이런 분기(Branch)가 반복되면 CPU는 미쳐버립니다.
최신 CPU는 파이프라인을 통해 명령어를 미리미리 처리하고 싶어 하는데, if 문이 나올 때마다 흐름이 끊기니까요.
저자는 이걸 해결하려고 AVX2 같은 SIMD(Single Instruction Multiple Data) 명령어를 꺼내 듭니다.
한 번에 문자 하나가 아니라, 16개~32개씩 뭉텅이로 처리하는 기술이죠.
이걸 구현하는 과정은 솔직히 말해 '맨땅에 헤딩'입니다.
레지스터에 데이터를 정렬하고, 비트 마스크(Bit Mask)를 씌우고...
거의 어셈블리어 수준의 끔찍한 코딩을 해야 합니다.
4. 뼈를 깎는 최적화의 결과
저자가 immintrin.h를 사용해 수작업으로 AVX2 최적화를 마쳤습니다.
코드 퀄리티는 유지보수를 포기한 수준이지만, 성능은 어떨까요?
결과: 1.45 GB/s.
기존 278 MB/s보다는 5배 넘게 빨라졌습니다. 엄청난 성과죠.
하지만 여기서 소름 돋는 포인트가 있습니다.
죽어라 최적화해서 1.45 GB/s를 만들었는데, 디스크가 줄 수 있는 속도(12.8 GB/s)의 11%밖에 안 됩니다.
단일 스레드(Single Thread)로는 CPU가 아무리 날고 기어도 최신 디스크의 속도를 못 따라간다는 뜻입니다.
5. 8년 차 개발자의 다짐
이 결과를 보고 저는 두 가지를 깨달았습니다.
첫째, "I/O 탓하지 말자."
이제 I/O는 충분히 빠릅니다. 느리다면 제 로직이 비효율적이거나, CPU를 제대로 활용하지 못하고 있을 확률이 높습니다.
둘째, "벡터화(Vectorization)는 선택이 아닌 필수다."
단순 루프문(for, while)으로는 현대 하드웨어의 성능을 10%도 못 씁니다.
물론 우리가 매일 AVX 인트린식 코드를 짤 순 없습니다. 생산성이 떨어지니까요.
하지만 Numpy나 Pandas 같은 라이브러리가 왜 빠른지, 내부적으로 어떻게 SIMD를 쓰는지 이해하는 건 중요합니다.
그리고 이제는 Cursor나 Claude 같은 AI 도구에게 "이 루프, SIMD로 최적화해줄 수 있어?"라고 물어볼 수 있는 시대입니다.
하드웨어는 이미 저만치 앞서가고 있습니다.
우리의 사고방식도 "DB가 느려"에서 "CPU를 놀리고 있진 않나?"로 바뀌어야 할 때입니다.
오늘 여러분의 코드는 CPU를 얼마나 괴롭히고 있나요?
한 번쯤 프로파일러(Profiler)를 돌려보시길 권합니다.
범인은 의외로 가까운 곳에 있을지도 모릅니다.


