I/O는 더 이상 병목이 아닌가?
(stoppels.ch)- 최근 논의된 I/O 성능과 CPU 처리 속도의 불균형을 실험으로 검증하며, 실제로는 여전히 CPU가 주요 제약임을 보여줌
- 순차 읽기 속도는 냉 캐시에서 1.6GB/s, 온 캐시에서 12.8GB/s에 달하지만, 단일 스레드의 단어 빈도 계산은 278MB/s 수준에 머무름
- 코드의 분기(branch) 구조가 벡터화(vectorization)를 방해하며, 단순한 소문자 변환 최적화로도 330MB/s 정도만 향상됨
-
wc -w명령어조차 245MB/s에 불과해, 디스크보다 CPU 연산 및 분기 처리가 병목임을 확인 - AVX2 기반 수동 벡터화로 1.45GB/s까지 끌어올렸지만, 여전히 순차 읽기 속도의 약 11% 수준으로, I/O가 아닌 CPU가 병목임을 입증
I/O 속도와 CPU 성능 비교
- Ben Hoyt의 주장에 따라, 최근의 순차 읽기 속도 향상이 CPU 속도 정체를 앞질렀는지 실험
- 동일한 방식으로 측정 시 냉 캐시 1.6GB/s, 온 캐시 12.8GB/s 기록
- 그러나 단일 스레드에서 단어 빈도 계산을 수행하면 278MB/s에 불과
- 이는 캐시가 따뜻한 상태에서도 디스크 읽기 속도의 약 1/5 수준
C 기반 단어 빈도 계산 실험
- GCC 12로
optimized.c를-O3 -march=native옵션으로 컴파일 후 425MB 입력 파일에서 실행- 결과: 1.525초 소요, 278MB/s 처리 속도
- 코드 내 다중 분기와 조기 종료가 컴파일러의 벡터화 최적화를 방해
- 소문자 변환 로직을 루프 외부로 이동한 후 330MB/s로 향상
- Clang 사용 시 벡터화가 더 잘 수행됨
단순 단어 세기(wc -w) 비교
- 빈도 계산 대신 단순 단어 수만 세는
wc -w명령어 실행- 결과: 245.2MB/s, 예상보다 느림
-
wc는' ','\n','\t'등 다양한 공백 문자와 로케일 문자를 처리- 단순 공백만 구분하는 코드보다 연산량이 많음
AVX2 기반 벡터화 시도
- 최신 CPU 기능을 활용해 AVX2 명령어 집합으로 벡터화 구현
- 256비트 레지스터 사용, 데이터 32비트 정렬
- 공백 문자 비교를 위해
VPCMPEQB명령어 사용
-
비트 마스크(PMOVMSKB) 와 Find First Set(ffs) 명령어로 단어 경계 탐지
- Cosmopolitan libc의
strlen구현에서 착안
- Cosmopolitan libc의
성능 결과 및 결론
- 수동 벡터화 코드(
wc-avx2)는 1.45GB/s 처리 속도 달성-
wc -w와 동일한 결과(82,113,300 단어) 검증
-
- 냉 캐시 상태에서도 여전히 user 모드 연산 시간이 지배적
- 디스크 I/O보다 CPU 연산이 병목임을 확인
- 전체적으로 디스크 속도는 충분히 빠르지만, 분기 처리와 해시 계산 등 CPU 연산이 한계 요인으로 남음
- 코드와 실험 결과는 GitHub(
haampie/wc-avx2)에 공개됨
Hacker News 의견들
-
현대 CPU의 성능 한계는 단일 코어가 처리할 수 있는 데이터 양, 즉
memcpy()속도에 의해 결정된다고 생각함
대부분의 x86 코어는 약 6GB/s, Apple M 시리즈는 약 20GB/s 수준임
광고에서 말하는 ‘200GB/s’ 같은 수치는 전체 코어 합산 대역폭일 뿐, 단일 코어는 여전히 6GB/s 근처에 머무름
따라서 완벽한 파서를 작성해도 이 한계를 넘을 수 없음
하지만 zero-copy 포맷을 사용하면 CPU가 불필요한 데이터를 건너뛸 수 있어 이론적으로 6GB/s를 ‘초과’할 수 있음
내가 개발 중인 Lite³ 포맷은 이 원리를 활용해 simdjson보다 최대 120배 빠른 성능을 보임- 제시한 단일 코어 수치는 너무 낮다고 생각함
예를 들어 Zen 1은 단일 코어에서 25GB/s를 보여줌(참고 링크)
내가 작성한 microbenchmark 결과에 따르면 Zen 2는 AVX 비사용 시 17GB/s, non-temporal AVX 사용 시 35GB/s까지 나옴
Apple M3 Max에서는 non-temporal NEON으로 125GB/s까지 측정됨
따라서 x86 6GB/s, Apple 20GB/s라는 수치는 실제보다 훨씬 낮음 - 이 한계가 어디서 오는지 궁금함 — 코어와 캐시, 혹은 메모리 컨트롤러 사이의 버스 구조 때문인지 질문함
- 왜 Apple M 시리즈가 x86보다 코어당 3배 높은 대역폭을 가지는지 궁금함
- 최신 칩에서는 CPU만으로는 메모리 대역폭을 포화시키기 어렵고, iGPU를 활용해야 가능함
iGPU가 통합 메모리에 접근할 수 있기 때문임
따라서 대용량 메모리 복사나 병렬 파싱, 압축/해제 같은 작업은 iGPU를 blitter로 활용하는 것이 기술적으로 유리함
다만 zero-copy 포맷에서 말하는 ‘건너뛰기’는 캐시라인 단위로 이루어짐 - 삼성 NVMe SSD가 14GB/s 읽기 속도를 광고하는데, CPU 단일 코어가 6GB/s라면 이 수치와의 관계가 흥미로움
- 제시한 단일 코어 수치는 너무 낮다고 생각함
-
원글 작성자가
time명령어의 출력을 잘못 해석한 것 같음
system시간은 커널이 프로세스를 대신해 사용한 CPU 시간임
예시에서real0.395s,user0.196s,sys0.117s라면 CPU는 총 313ms 동안만 일했고, 나머지 82ms는 유휴 상태였음
즉, 디스크 서브시스템보다 빠르게 동작하긴 했지만 차이는 크지 않음
또한 I/O 경로가 CPU 바운드 상태임 — 디스크와 코드가 무한히 빠르더라도 커널 I/O 코드 실행에 117ms가 필요함 -
글 작성자임. 후속편이 있음: I/O is no longer the bottleneck, part 2
- 예전에 단어 빈도수 계산 대회에 참가한 적이 있음
참가자들이 사용한 다양한 최적화 기법을 다룬 분석 글이 흥미로움
문제의 복잡도나 공백 문자 분류 수에 따라 접근 방식이 달라졌음 - 만약 이 테스트가 단일 코어에서 수행된 것이라면, 위에서 주장한 “6GB/s 한계”는 실험적으로 반박된 셈임
- 예전에 단어 빈도수 계산 대회에 참가한 적이 있음
-
성능 병목은 항상 “CPU냐 I/O냐” 같은 단일 요인이 아니라, 실제 워크로드에서 가장 먼저 포화되는 자원임
CPU, 메모리 대역폭, 캐시, 디스크, 네트워크, 락, 지연 등 중 어느 것이든 될 수 있음
따라서 측정하고, 프로파일링으로 증명하고, 변경 후 다시 측정해야 함 -
문제는 CPU나 I/O가 아니라 지연(latency) 과 처리량(throughput) 의 균형임
대부분의 소프트웨어는 지연을 무시해서 느림
데이터를 메모리에 선형적으로 배치하거나, 배치 처리와 병렬화를 적용하면 훨씬 빨라짐 -
CPU ↔ 캐시 ↔ 비휘발성 스토리지 구조만으로 구성된 아키텍처를 상상해봄
mmap()이malloc()과 동일한 성능 특성을 가진다면, 프로그램 메모리를 파일 이름으로 지정해 OS에 영속성을 맡길 수도 있을 것임
여전히 많은 소프트웨어 설계가 하드디스크 시대의 제약에 묶여 있음- 하지만
fsync()는 여전히 느림
진짜 영속성을 위해서는 회전 디스크 여부와 관계없이 다른 접근이 필요함 - 리눅스에서도 비슷한 걸 구현할 수 있음
실제로 대부분의 메모리 요청은mmap()을 통해 이루어짐
다만 커널이 접근 패턴을 예측하기 어려워read/write보다 느릴 수 있음
- 하지만
-
클라우드 환경에서는 성능이 요금제 조정 수단이 되기도 함
하드웨어 성능은 놀라울 정도로 발전했지만, 일부 소프트웨어(특히 Windows나 메신저 앱)는 오히려 더 느리게 느껴짐- 실제로 클라우드 인스턴스 성능은 M1 MacBook보다 5배 느리면서도 훨씬 비쌈
개발자 원격 워크스테이션으로는 비효율적임 - 대부분의 GUI 앱이 느린 이유는 여전히 I/O 대기 때문임
Telegram이나 FB Messenger는 빠르지만 Teams나 Skype는 그렇지 않음 - CRT 모니터는 데이터를 더 빨리 표시했음
일부 LCD는 500ms 지연이 있음
- 실제로 클라우드 인스턴스 성능은 M1 MacBook보다 5배 느리면서도 훨씬 비쌈
-
NVMe SSD가 처음 나왔을 때 “이제 2TB RAM을 가진 셈”이라고 농담했음
그런데 요즘 GPU 서버는 실제로 2TB RAM을 탑재함 — 놀라운 엔지니어링임- 예전에 중고 Epyc 서버에서 2TB DDR4 RAM 구성을 5천 달러에 본 적 있음
그때 사둘 걸 아쉬워함
- 예전에 중고 Epyc 서버에서 2TB DDR4 RAM 구성을 5천 달러에 본 적 있음
-
OLAP 데이터베이스를 고도 동시성 환경에서 최적화한 경험상, 병목은 대부분 메모리 속도였음
-
I/O 병목은 원래 순차 읽기가 아니라 탐색(seek) 시간과 관련된 개념이었음
글의 요지는 이해하지만, 이 점은 짚고 싶음- CXL/PCIe 같은 최신 기술 덕분에 이제 RAM과 메모리 컨트롤러도 일종의 I/O 장치로 간주할 수 있음
- 예전 데이터베이스 수업에서 I/O 성능은 하드디스크의 탐색 시간으로 측정했음
순차 읽기 속도는 코드로 개선할 수 없으니, 비순차 접근 최적화가 핵심이었음