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 자원을 동시에 활용하며 전체 파이프라인의 공회전을 줄일 수 있음.