CPU 교체 후 테스트 - 100-hours-a-week/5-yeosa-wiki GitHub Wiki
1. 개요
-
이미지 디코딩 + 전처리 작업에서 CPU 병목 지점 발견
-
CPU 사용량은 90% 이상으로 유지되는 반면, GPU 사용량은 40% 대에서 머무르는 것을 확인
-
-
기존에 사용하던 CPU(n1-standard-4. 물리 코어 1, 논리 코어 4)를 물리코어 4, 논리 코어 8인 CPU로 업그레이드
-
CPU 성능 문제가 병목의 원인이었는지 재확인 필요
[ 요약 ]
항목 | 내용 |
---|---|
문제 인식 | 이미지 디코딩 + 전처리 시 CPU 사용률이 90% 이상으로 치솟고, GPU는 20% 수준에 머무름 |
가설 | CPU 병목이 전체 파이프라인 지연의 주요 원인일 수 있음 |
실험 조건 | 기존: n1-standard-4 (물리코어 1, 논리코어 4)변경: 고성능 VM (물리코어 4, 논리코어 8) |
목표 | CPU 코어 수 증가 시 디코딩/전처리/총시간의 병목이 해소되는지 검증 |
2. 테스트 결과
a. 방식 설명
- 방식 1 : CPU 전처리 + 개별 전처리
- 방식 2 : CPU 전처리 + 배치 전처리
- 방식 3 : GPU 전처리 + 개별 전처리
- 방식 4 : GPU 전처리 + 배치 전처리
b. 업그레이드 전(avg / min / max)
항목 | 방식 1 | 방식 2 | 방식 3 | 방식 4 |
---|---|---|---|---|
로딩 | 193.42ms / 15.66ms / 1210.00ms | 166.47ms / 12.42ms / 1200.00ms | 179.09ms / 34.02ms / 1220.00ms | 160.44ms / 15.23ms / 1210.00ms |
디코딩 완료 | 24.15ms / 8.63ms / 61.05ms | 24.70ms / 8.14ms / 235.81ms | 24.51ms / 9.41ms / 226.76ms | 23.84ms / 8.90ms / 227.68ms |
디코딩 대기 | 157.83ms / 0.97ms / 485.80ms | 126.49ms / 1.13ms / 396.29ms | 148.06ms / 5.64ms / 499.23ms | 130.87ms / 4.15ms / 458.42ms |
로딩 + 디코딩 | 578.19ms / 279.31ms / 1320.00ms | 495.21ms / 297.76ms / 1350.00ms | 563.29ms / 319.48ms / 1490.00ms | 472.69ms / 242.70ms / 1340.00ms |
전처리 | 170.98ms / 81.45ms / 274.80ms | 247.26ms / 142.95ms / 360.14ms | 97.43ms / 64.28ms / 283.45ms | 180.08ms / 129.03ms / 301.51ms |
임베딩 | 405.64ms / 144.31ms / 616.29ms | 297.05ms / 212.36ms / 425.68ms | 312.68ms / 219.34ms / 440.88ms | 330.30ms / 137.18ms / 484.53ms |
총 처리 시간 | 1320.69ms / 622.30ms / 2110.00ms | 1138.68ms / 762.49ms / 2010.00ms | 1145.00ms / 733.69ms / 2110.00ms | 1077.71ms / 535.62ms / 1950.00ms |
c. 업그레이드 후(avg / min / max)
항목 | 방식 1 | 방식 2 | 방식 3 | 방식 4 |
---|---|---|---|---|
로딩 | 143.12ms / 14.04ms / 807.46ms | 119.20ms / 11.62ms / 1050.00ms | 152.21ms / 15.42ms / 936.67ms | 133.62ms / 11.59ms / 936.47ms |
디코딩 완료 | 16.69ms / 8.29ms / 59.00ms | 16.39ms / 7.77ms / 48.14ms | 18.38ms / 8.84ms / 64.09ms | 17.45ms / 7.99ms / 202.10ms |
디코딩 대기 | 87.96ms / 1.39ms / 301.21ms | 70.92ms / 0.49ms / 315.62ms | 105.12ms / 0.98ms / 360.75ms | 79.77ms / 2.31ms / 343.29ms |
로딩 + 디코딩 | 335.63ms / 196.78ms / 878.86ms | 290.97ms / 149.81ms / 1070.00ms | 397.83ms / 158.04ms / 1060.00ms | 346.41ms / 140.60ms / 979.72ms |
전처리 | 127.85ms / 66.20ms / 354.46ms | 178.47ms / 120.27ms / 376.09ms | 95.92ms / 59.63ms / 160.56ms | 100.72ms / 62.54ms / 188.99ms |
임베딩 | 465.10ms / 225.93ms / 885.34ms | 363.31ms / 209.16ms / 474.08ms | 464.01ms / 240.74ms / 648.18ms | 450.69ms / 239.89ms / 614.65ms |
총 소요 시간 | 978.49ms / 541.15ms / 1830.00ms | 882.24ms / 541.07ms / 1670.00ms | 1020.61ms / 526.84ms / 1760.00ms | 962.27ms / 545.63ms / 1570.00ms |
d. 성능 변화율 요약(avg / min / max)
구분 | 방식 1 | 방식 2 | 방식 3 | 방식 4 |
---|---|---|---|---|
로딩 | -26.0% / -10.4% / -33.3% | -28.4% / -6.4% / -12.5% | -15.1% / -54.7% / -23.2% | -16.9% / -23.9% / -22.6% |
디코딩 완료 | -30.9% / -3.9% / -3.4% | -33.6% / -4.5% / -79.6% | -25.0% / -6.1% / -71.7% | -26.8% / -10.2% / -11.7% |
디코딩 대기 | -44.3% / +43.3% / -38.0% | -43.9% / -56.6% / -20.3% | -29.0% / -82.6% / -27.7% | -39.1% / -44.3% / -25.1% |
로딩+디코딩 | -41.9% / -29.5% / -33.4% | -41.2% / -49.7% / -21.5% | -29.4% / -28.9% / -28.8% | -26.7% / -27.0% / -28.2% |
전처리 | -25.2% / -18.7% / +29.0% | -27.8% / -15.9% / +4.4% | -1.6% / -7.2% / -43.4% | -44.1% / -51.5% / -37.3% |
임베딩 | +14.7% / +56.5% / +43.6% | +22.3% / +35.0% / +11.4% | +48.4% / +9.8% / +47.1% | +36.5% / +9.1% / +26.8% |
총 소요 시간 | -25.9% / -13.1% / -13.3% | -22.5% / -16.2% / -14.9% | -10.9% / -8.3% / -16.6% | -10.7% / -19.5% / -19.5% |
이미지 로딩 | 디코딩 대기 | 디코딩 | 전처리 | 임베딩 | 총 소요시간 | |
---|---|---|---|---|---|---|
업그레이드 전 | 160.44ms | 130.87ms | 23.84ms | 180.08ms | 330.30ms | 1077.71ms |
업그레이드 후 | 133.62ms | 79.77ms | 17.45ms | 100.72ms | 450.69ms | 962.27ms |
변화율 | -16.9% | -39.1% | -26.8% | -44.1% | +36.42% | -19.5% |
3. 분석
a. 로딩 + 디코딩 개선
가. 개선 결과 요약(avg 기준)
항목 | 업그레이드 전 평균 | 업그레이드 후 평균 | 개선 |
---|---|---|---|
로딩 | 175.65ms | 137.04ms | -22.0% |
디코딩 완료 | 24.30ms | 17.73ms | -27.0% |
디코딩 대기 | 140.81ms | 85.94ms | -38.9% |
로딩+디코딩 | 527.10ms | 342.21ms | -35.1% |
나. 분석
[ 로딩 평균 시간 -22% 감소 ]
- 기존 문제: 스레드를 병렬로 생성하더라도, 물리 코어 1개 CPU에서는 실질적인 동시 다운로드가 제한됨.
- 개선 이유: CPU 업그레이드 후 여러 네트워크 요청이 병렬로 처리되어 로딩 시간이 평균적으로 짧아짐.
[ 디코딩 완료 평균 시간 -27% 감소 ]
- 기존 문제: 디코딩 연산은 OpenCV 기반으로 CPU 연산인데, 낮은 코어 수에서는 직렬화되거나 지연됨.
- 개선 이유: 업그레이드된 다중 코어 환경에서 디코딩 연산이 병렬로 수행되며 평균 처리 시간이 낮아짐.
[ 디코딩 대기 평균 시간 -39% 감소 ]
- 기존 문제: 디코딩 순서가 밀려서 뒤늦게 디코딩되는 이미지들의 대기 시간이 매우 길어짐.
- 개선 이유: 더 많은 코어가 디코딩 작업을 동시에 처리하면서, 대기 큐에서 기다리는 이미지가 줄어듦.
[ 로딩+디코딩 평균 시간 -35% 감소 ]
- 총합적인 효과: 로딩, 디코딩 완료, 디코딩 대기 모두 개선 → 로딩+디코딩 전체 평균 시간도 크게 줄어듦.
- 특히 **방식 1/3 (스레드 다중 생성)**에서는 코어 부족으로 인한 스레드 스케줄링 병목이 해소된 것이 큰 기여 요인.
b. 전처리 개선
가. 개선 결과 요약(avg 기준)
방식 | 업그레이드 전 avg | 업그레이드 후 avg | 개선률 (↓) |
---|---|---|---|
방식 1 | 170.98ms | 127.85ms | -25.25% |
방식 2 | 247.26ms | 178.47ms | -27.81% |
방식 3 | 97.43ms | 95.92ms | -1.55% |
방식 4 | 180.08ms | 100.72ms | -44.07% |
나. 분석
[ 방식 1 & 방식 2 (CPU 전처리 기반) ]
- 개선 폭: 각각 평균 25%, 28% 감소
- 원인 분석:
- 업그레이드 전에는 이미지 수만큼 스레드를 생성했지만, 물리 코어 1개 환경에서는 실질적으로 동시에 실행되지 못해 병목이 발생.
- 업그레이드 후에는 4코어 8스레드 CPU가 각 스레드의 전처리를 병렬 처리하며 처리 속도 향상.
[ 방식 3 (GPU에서 개별 전처리) ]
- 개선 폭: 약 1.5% (미미)
- 원인 분석:
- 전처리 대부분이 GPU에서 수행되므로 CPU 업그레이드의 직접적인 영향이 거의 없음.
- 단, 이미지 로딩 및 GPU 전송 준비 등 CPU 부하가 약간 완화되며 소폭 개선됨.
[ 방식 4 (GPU에서 배치 전처리) ]
- 개선 폭: 평균 44% 감소 (가장 큰 개선폭)
- 원인 분석:
- 전처리 계산 자체는 GPU에서 수행되지만, 배치 구성, 텐서 변환, 전송 과정은 CPU의 지원이 필요.
- 업그레이드 전에는 이 부분이 병목이었으나, CPU가 강화되면서 병목이 크게 해소됨.
- 또한 방식 4는 배치 단위로 GPU 메모리에 효율적으로 접근하므로, 전처리 효율성이 극대화됨.
c. 임베딩 악화
가. 결과 요약(avg 기준)
방식 | 업그레이드 전 avg | 업그레이드 후 avg | 변화율 (↑) |
---|---|---|---|
방식 1 | 405.64ms | 465.10ms | +14.67% |
방식 2 | 297.05ms | 363.31ms | +22.34% |
방식 3 | 312.68ms | 464.01ms | +48.39% |
방식 4 | 330.30ms | 450.69ms | +36.42% |
나. 분석
[ 원인 1. CPU 전처리 속도 향상으로 인한 GPU 연산 요청 집중 ]
- CPU 업그레이드로 인해 전처리 시간이 큰 폭으로 개선되었고, 결과적으로 이미지 텐서가 GPU에 더 빠르게 전달됨.
- 이로 인해 임베딩 연산 요청이 짧은 간격으로 GPU에 집중적으로 몰리는 현상이 발생.
- GPU는 요청이 몰릴수록 커널 호출 및 전환 비용이 증가하게 되며, 이는 곧 전체 임베딩 처리 시간 증가로 이어짐.
[ 원인 2. GPU 전처리 방식의 연산 집중 구조 ]
- 방식 3, 4는 전처리와 임베딩을 모두 GPU에서 수행하므로, 하나의 디바이스에 연산 부하가 집중됨.
- CPU에서 처리하던 일부 연산이 GPU로 이전되면서, GPU의 커널 실행 스케줄링 충돌 및 메모리 대역폭 경쟁이 심화됨.
- 결과적으로 방식 1, 2보다 방식 3, 4에서 임베딩 시간 증가 폭이 더 크게 나타남.
[ 원인 3. 배치 크기 16의 연산 효율 부족 ]
- 현재 배치 크기인 16은 GPU의 병렬 처리 성능을 충분히 활용하기엔 작음.
- 연산량이 작아 GPU 자원이 일부만 사용되고, 작은 요청들이 빠르게 반복되며 커널 초기화/스케줄링 비용만 증가.
- 특히 연속적인 요청 흐름에서는 오히려 GPU 리소스를 낭비하게 되어 임베딩 성능이 저하될 수 있음.
d. 총 소요 시간 개선
가. 결과 요약(avg 기준)
방식 | 업그레이드 전 (avg) | 업그레이드 후 (avg) | 변화율 |
---|---|---|---|
방식 1 | 1320.69ms | 978.49ms | -25.91% |
방식 2 | 1138.68ms | 882.24ms | -22.52% |
방식 3 | 1145.00ms | 1020.61ms | -10.86% |
방식 4 | 1077.71ms | 962.27ms | -10.71% |
나. 분석
[ CPU 병목 해소가 총 처리 시간에 큰 영향 ]
- 방식 1, 2는 이미지 디코딩과 전처리 전부 또는 대부분을 CPU에서 수행하는 구조이므로, CPU 업그레이드의 직접적인 수혜를 받음.
- 특히 방식 1은 디코딩 + 전처리에 스레드 과다 생성으로 인한 컨텍스트 스위칭 병목이 있었는데, 물리 코어 증가로 이 병목이 크게 해소되어 총 시간 개선이 **약 26%**로 가장 큼.
[ GPU 중심 방식은 개선폭이 작음 ]
- 방식 3, 4는 GPU에서 전처리까지 담당하는 구조로 CPU 의존도가 낮았기 때문에, 업그레이드의 영향이 상대적으로 적음.
- 총 소요 시간 감소는 디코딩 대기 시간 감소 등의 간접 효과에 기인하며, 전처리 및 임베딩 시간은 일부 증가 또는 소폭 개선에 그침.
- 결과적으로 총 시간 개선율은 약 10% 수준에 머물렀음.
[ 임베딩 시간 증가가 전체 개선폭을 일부 상쇄 ]
- 모든 방식에서 임베딩 시간이 오히려 증가하였고, 특히 방식 3과 4는 GPU 연산 집중에 따른 임베딩 병목으로 개선폭이 제한됨.
- 따라서 전처리나 디코딩에서의 시간 절감이 전반적 성능 개선으로 이어졌지만, GPU의 과도한 연산 집중은 병목을 재형성함.
4. 개선 방향
a. 배치 크기 확장
가. 현재 상황
-
GPU 사용률이 60%까지 상승한 것은 긍정적인 신호이지만, 동시에 의미하는 바는 다음과 같음:
- 작은 배치 단위로 여러 번 GPU에 데이터를 올리고 연산을 수행하고 있음.
- 이로 인해
to("cuda")
연산이 자주 발생하고, GPU 커널도 짧은 작업을 빈번히 스케줄링하게 됨.
나. 개선 전략
- 배치 크기 16 → 32 → 64 순으로 실험하면서 메모리 사용률과 처리 시간 간 균형점을 찾아보는 것이 필요
b. 동시 처리 최적화를 위한 스케줄링 개선
가. 현재 상황
-
업그레이드 후 CPU 코어 수는 충분하지만, 전체 파이프라인의 구조는 아직 일괄 처리(batch-then-next-step) 방식.
# 전체 전처리 작업 후 for i in range(0, len(images), batch_size): ... preprocessed_batch = preprocess(batch_images) ... # 배치 단위로 임베딩 수행 for i, batch in enumerate(preprocessed_batches): ... with torch.no_grad(): batch_features = model.encode_image(batch) ...
- 즉, 로딩 → 전처리 → 임베딩이 순차적으로 진행되고 있음.
나. 개선 전략
- CPU에서 다음 배치 로딩 및 전처리를 수행하는 동안, 이전 배치의 GPU 임베딩을 병렬로 처리하는 구조로 개편 (예:
asyncio
,concurrent.futures
, 또는 task queue 구조). - 이를 통해 CPU와 GPU 자원을 동시에 활용하며 전체 파이프라인의 공회전을 줄일 수 있음.