포스트

병렬처리는 왜 코어를 늘려도 계속 빨라지지 않을까: CPU, GPU, FPGA 선택 기준

코어 수를 늘려도 프로그램 전체가 같은 비율로 빨라지지는 않으며, 먼저 병렬화할 수 없는 구간과 가속기에 맡길 계산을 구분해야 합니다. 같은 연산을 많은 데이터에 반복한다면 GPU가, 규칙적인 데이터 흐름을 하드웨어에 가깝게 고정해야 한다면 FPGA가 후보가 됩니다. 그러나 계산보다 데이터 이동과 동기화에 시간이 더 들면 가속기를 추가해도 전체 실행 시간은 거의 줄지 않습니다.

클록 경쟁이 멀티코어와 가속기로 바뀐 이유

CPU의 클록을 높이면 더 많은 전력을 쓰고 열도 늘어납니다. 이 전력, 발열 장벽 때문에 한 코어만 더 빠르게 만드는 대신 여러 코어가 일을 나누는 방향이 중요해졌습니다. 다만 코어가 여러 개라는 사실만으로 기존 프로그램이 자동으로 병렬화되지는 않습니다. 작업을 서로 독립적인 단위로 나눌 수 있어야 동시에 처리할 수 있습니다.

GPU와 FPGA는 CPU를 단순히 대체하는 장치가 아닙니다. CPU가 일반적인 명령과 흐름 제어를 맡고, GPU나 FPGA가 계산량이 큰 특정 작업을 처리하는 구성이 이종 컴퓨팅 시스템입니다. 따라서 장치를 고를 때는 “무엇이 더 빠른가”보다 다음 질문이 먼저입니다.

  • 같은 계산을 대량으로 반복할 수 있는가?
  • 작업 사이의 의존성을 끊어 동시에 실행할 수 있는가?
  • 일반 제어는 CPU에 남기고 계산 집중 구간만 분리할 수 있는가?

이 질문에 답하지 않은 채 가속기만 추가하면 병렬화의 이득을 예측하기 어렵습니다.

Amdahl의 법칙과 파이프라인이 알려주는 한계

Amdahl의 법칙은 프로그램에서 병렬화 가능한 부분과 불가능한 부분의 비율, 사용하는 프로세서 수를 바탕으로 최대 성능 향상을 생각하게 합니다. 핵심은 간단합니다. 순차 실행이 필요한 부분이 남아 있으면 프로세서를 계속 추가해도 전체 속도 향상에는 한계가 생깁니다. 최적화 대상을 고를 때 실행 시간의 대부분을 차지하면서 실제로 나눌 수 있는 구간부터 찾아야 하는 이유입니다.

CPU 내부에서도 명령은 가져오기(Fetch), 해석하기(Decode), 실행하기(Execute) 단계로 처리됩니다. 파이프라이닝은 여러 명령이 서로 다른 단계를 동시에 지나가게 해 처리량을 높입니다. 명령 순서를 지키는 In-order 실행과 준비된 명령을 먼저 처리하는 Out-of-Order 실행, 여러 명령을 함께 실행하는 Superscalar 구조는 “한 작업을 여러 조각으로 겹쳐 처리한다”는 병렬성의 또 다른 모습입니다.

OpenCL과 CUDA도 같은 맥락에서 봐야 합니다. OpenCL은 다양한 플랫폼의 병렬 컴퓨팅을 대상으로 하고, CUDA는 NVIDIA GPU를 대상으로 합니다. 도구 이름보다 먼저 데이터와 연산을 어떻게 나눌지 결정해야 합니다.

Thor 작업 명령을 읽을 때 확인할 것

겨울학교 환경에서는 thorq로 작업을 제출하고 상태와 결과를 확인했습니다. 아래 명령은 당시 환경의 기록이며, ./exec 실행 파일과 작업 번호가 실제로 존재할 때만 의미가 있습니다.

1
2
3
thorq --add --mode single ./exec 10
thorq --stat 400000
cat task_400000.stdout

GPU 장치를 지정하거나 실행 시간을 제한하는 형태도 있었습니다.

1
2
thorq --add --mode single --device gpu/7970 ./exec
thorq --add --mode single --timeout 100 ./exec

전체 작업을 종료하는 thorq --kill-all은 다른 작업에도 영향을 줄 수 있으므로 대상과 권한을 확인한 뒤 사용해야 합니다. thorq --stat-all은 전체 상태 확인용입니다.

다음 표기는 완성된 실행 명령이 아니라 인자 구조를 보여주는 핵심 조각입니다. 꺾쇠괄호 부분을 실제 파일명과 값으로 바꾸고, 해당 스크립트가 있는 디렉터리에서 실행해야 합니다.

1
2
3
./plot_input.py <입력파일> <new.png>
./plot_output.py <입력파일> <출력파일> <new.png>
./gen_input.py <점개수> <클러스터개수> <생성할입력파일>

병렬 프로그램을 점검할 때는 코어 수부터 늘리기보다 순차 구간, 작업 분할 가능성, 가속기 적합성, 스케줄러의 실제 실행 파일과 결과 경로를 차례로 확인하는 편이 안전합니다.

가속기 도입 여부는 어떤 순서로 판단해야 하나

먼저 실제 실행 시간을 측정해 가장 오래 걸리는 구간을 찾습니다. 그 구간이 파일 입출력이나 네트워크 대기라면 계산 장치를 바꾸는 것보다 데이터 경로를 고치는 편이 우선입니다. 계산이 병목이라도 앞뒤 작업의 결과를 기다려야 한다면 여러 코어에 나누기 어렵습니다. 반대로 각 입력을 독립적으로 처리하고 같은 연산을 반복한다면 병렬화 후보가 됩니다.

다음에는 계산과 데이터 이동을 분리해 봅니다. CPU 메모리에서 가속기 메모리로 입력을 옮기고 결과를 되가져오는 시간까지 포함해야 합니다. 작은 작업을 매번 전송하면 계산이 빨라도 전송 준비 비용이 이득을 삼킬 수 있습니다. 이때는 여러 입력을 묶거나, 중간 결과를 장치에 남겨 두거나, CPU에서 처리하는 편이 나은지 비교해야 합니다.

마지막으로 평균 속도만 보지 말고 운영 조건을 함께 봅니다. GPU는 범용 병렬 연산과 개발 도구가 필요한 경우에 유리하고, FPGA는 고정된 파이프라인과 예측 가능한 지연이 중요한 경우에 검토할 수 있습니다. 어느 장치든 코드 변경 비용, 지원 라이브러리, 전력, 메모리 용량, 장애 시 대체 경로까지 포함해야 실제 선택 기준이 됩니다.

함께 읽으면 이해가 이어지는 글

자주 묻는 질문

코어 수를 두 배로 늘리면 프로그램도 두 배 빨라지나요?

아닙니다. 순차 실행 구간과 동기화, 통신 비용이 남기 때문에 병렬화 가능한 계산만 빨라지며, 전체 향상 폭은 Amdahl의 법칙이 설명하는 상한을 가집니다.

GPU와 FPGA 중 무엇을 먼저 검토해야 하나요?

같은 연산을 많은 데이터에 반복한다면 GPU가 자연스럽고, 고정된 데이터 흐름과 지연 시간, 전력 제약이 중요하다면 FPGA를 검토할 수 있습니다. 어느 쪽이든 CPU와 장치 사이의 데이터 이동 비용을 먼저 계산해야 합니다.

글의 thorq 명령을 지금 그대로 실행해도 되나요?

아닙니다. 이 명령은 당시 겨울학교 스케줄러 환경의 기록입니다. 실행 파일, 작업 번호, 장치 이름이 실제 환경에 존재하는지 확인하고, 특히 kill-all 같은 전체 종료 명령은 영향 범위를 확인해야 합니다.

영상 처리 작업을 예로 들면 병목은 어떻게 드러나나

카메라 frame을 읽어 크기를 바꾸고, 같은 filter를 적용한 뒤 결과를 저장하는 작업을 가정해 봅니다. 전체 시간만 재면 “영상 처리가 느리다”는 사실밖에 알 수 없습니다. 입력 읽기, resize, filter, 결과 저장을 따로 측정하면 계산 가속이 가능한 부분과 그렇지 않은 부분이 갈립니다. Filter가 대부분의 시간을 차지하고 각 pixel이나 frame을 독립적으로 계산할 수 있다면 GPU 후보가 됩니다. 저장 시간이 대부분이라면 GPU를 추가해도 디스크를 기다리는 시간은 줄지 않습니다.

다음으로 작업 크기를 바꿔 측정합니다. 한 장씩 장치로 보내는 경우와 여러 장을 묶는 경우를 비교하면 전송 준비 비용이 드러납니다. 계산량이 작은 한 장에서는 CPU가 더 빨라 보이고, 충분히 큰 묶음에서는 병렬 장치가 유리할 수 있습니다. 이 결과는 “GPU가 빠르다”가 아니라 어느 크기부터 전송 비용을 상쇄하는지 알려 줍니다.

FPGA는 같은 흐름이 오래 유지되는지도 봐야 합니다. 전처리 규칙과 데이터 경로가 자주 바뀐다면 하드웨어 파이프라인을 바꾸는 비용이 커집니다. 반대로 입력 형식과 연산 순서가 고정돼 있고 처리량, 지연, 전력 조건이 명확하다면 검토 근거가 생깁니다. 개발 난도까지 포함하지 않고 연산 속도만 비교하면 실제 도입 비용을 과소평가하게 됩니다.

측정표에는 최소한 입력 크기, batch 크기, CPU 시간, 장치 전송 시간, 가속기 계산 시간, 결과 회수 시간을 따로 적습니다. 최종 시간은 이 항목의 합과 겹쳐 실행할 수 있는 구간에 의해 결정됩니다. 코어 수를 바꿀 때도 같은 표를 유지해야 병렬 구간이 빨라진 것인지 단순한 측정 변동인지 구분할 수 있습니다.

마지막으로 실패 시 되돌릴 경로를 정합니다. 가속기를 사용할 수 없을 때 CPU 구현이 같은 결과를 내는지, 정밀도 차이가 허용되는지, 장치 메모리를 넘는 입력은 어떻게 나눌지를 미리 정해야 합니다. 이 조건까지 만족해야 벤치마크 한 번이 아니라 운영 가능한 가속으로 볼 수 있습니다.

병렬화 전후의 결과가 같은지도 반드시 확인합니다. 작업 순서가 달라지면서 공유 데이터가 덮이거나 일부 입력이 두 번 처리될 수 있고, 부동소수점 합산 순서가 달라져 결과 차이가 생길 수도 있습니다. 허용 오차와 처리한 입력 수를 함께 검사해야 빠르지만 틀린 실행을 성능 향상으로 기록하지 않습니다.

스케줄러 환경에서는 요청한 코어, GPU와 실제 할당 장치를 로그에 남깁니다. 대기 시간과 실행 시간을 분리하고, 다른 사용자의 작업 부하가 측정에 영향을 줄 수 있다는 점도 고려합니다. 같은 조건을 여러 번 반복해 중앙 경향을 봐야 장치 선택과 일시적인 클러스터 상태를 구분할 수 있습니다.

최종 결정에는 “가속하지 않는 선택”도 포함합니다. 전체 시간에서 병렬화 가능한 비율이 작거나 데이터 이동이 지배적이면 CPU 코드의 알고리즘과 메모리 접근을 고치는 편이 단순할 수 있습니다. 장치 도입 여부를 측정 결과로 설명할 수 있어야 합니다.

THE END / OPSOAI

여기까지 읽었습니다

핵심 장면을 한 번 더 떠올려 보세요. 이해가 남았다면 이 책은 제 역할을 다했습니다.

다른 책 고르기
표지 1

키와 좌우 스와이프를 지원합니다. 읽던 페이지는 이 기기에 저장됩니다.

CONTENTS

이 책의 목차

    8개 장 18 분읽는 시간