dev_AI_framework

GPU, CPU 실행 시간 차이 확인...

명징직조지훈 2024. 10. 29. 09:18
#include <cuda_runtime.h>
#include <iostream>
#include <chrono>

__global__ void multiplyByTwoGPU(int *a, int size) {
    int idx = threadIdx.x + blockDim.x * blockIdx.x;
    if (idx < size) {
        a[idx] *= 2;
    }
}

void multiplyByTwoCPU(int *a, int size) {
    for (int i = 0; i < size; i++) {
        a[i] *= 2;
    }
}

int main() {
    const int size = 1000000; // 데이터 크기 증가
    int *h_a = new int[size];
    for (int i = 0; i < size; i++) {
        h_a[i] = i;
    }

    // GPU 연산 시간 측정
    int *d_a;
    cudaMalloc(&d_a, size * sizeof(int));
    cudaMemcpy(d_a, h_a, size * sizeof(int), cudaMemcpyHostToDevice);

    auto start_gpu = std::chrono::high_resolution_clock::now();
    int blockSize = 256;
    int numBlocks = (size + blockSize - 1) / blockSize;
    multiplyByTwoGPU<<<numBlocks, blockSize>>>(d_a, size);
    cudaDeviceSynchronize();  // 커널 실행이 끝날 때까지 대기
    auto end_gpu = std::chrono::high_resolution_clock::now();
    std::chrono::duration<float, std::milli> gpu_duration = end_gpu - start_gpu;

    cudaMemcpy(h_a, d_a, size * sizeof(int), cudaMemcpyDeviceToHost);
    cudaFree(d_a);

    // CPU 연산 시간 측정
    auto start_cpu = std::chrono::high_resolution_clock::now();
    multiplyByTwoCPU(h_a, size);
    auto end_cpu = std::chrono::high_resolution_clock::now();
    std::chrono::duration<float, std::milli> cpu_duration = end_cpu - start_cpu;

    // 결과 출력
    std::cout << "GPU 실행 시간: " << gpu_duration.count() << " ms" << std::endl;
    std::cout << "CPU 실행 시간: " << cpu_duration.count() << " ms" << std::endl;

    delete[] h_a;
    return 0;
}

CUDA 를 사용하여 GPU 연산과 일반 CPU 연산 간의 실행 시간을 측정할 수 있는 코드 예제,

각 배열 요소를 두 배로 곱하는 작업, 

  1. multiplyByTwoGPU : GPU 에서 각 요소를 두 배로 만드는 커널 함수
  2. multiplyByTwoCPU : CPU 에서 배열의 각 요소를 두 배로 만드는 함수
  3. main() 함수
    1. h_a 배열을 생성, 초기화
    2. GPU 연산 : cudaMalloc 으로 GPU 메모리를 할당하고 데이터 복사, 

 

처음 배열의 크기가 작을 땐 CPU 의 실행 시간이 GPU 의 실행시간보다 약 10 배 정도 빠르게 나왔음, 이는 데이터 크기가 작았고

GPU 로 데이터를 보내고 받는 데 소요되는 시간이 계산 시간보다 더 많이 걸릴 수 있다. - GPU 연산을 여러 번 반복하거나, 전송 비용을 한 번으로 줄일 수 있도록 여러 연산을 병합한다.

blockSize, numBlocks 의 조절

 

또는 커널 함수에서 연산이 단순하여 GPU 의 강점을 발휘하지 못하는 것일 수 있음, 복잡한 연산일수록 GPU 의 성능이 더 잘 발휘된다.

 

CUDA 초기화 시간

첫 번째 CUDA 호출에는 초기화 시간이 포함되어 더 많은 시간이 소요될 수 있다.

GPU 연산을 반복하여 초기화 시간이 결과에 미치는 영향을 줄일 수 있다.

 

대규모 행렬 곱셉 연산 코드에서의 확인

#include <cuda_runtime.h>
#include <iostream>
#include <chrono>

#define N 512  // 행렬 크기 (N x N)

// GPU에서 실행되는 커널 함수
__global__ void matrixMultiply(const float *A, const float *B, float *C, int n) {
    int row = blockIdx.y * blockDim.y + threadIdx.y;
    int col = blockIdx.x * blockDim.x + threadIdx.x;
    
    if (row < n && col < n) {
        float sum = 0;
        for (int k = 0; k < n; k++) {
            sum += A[row * n + k] * B[k * n + col];
        }
        C[row * n + col] = sum;
    }
}

// CPU에서 실행되는 행렬 곱셈 함수 (비교용)
void matrixMultiplyCPU(const float *A, const float *B, float *C, int n) {
    for (int row = 0; row < n; row++) {
        for (int col = 0; col < n; col++) {
            float sum = 0;
            for (int k = 0; k < n; k++) {
                sum += A[row * n + k] * B[k * n + col];
            }
            C[row * n + col] = sum;
        }
    }
}

int main() {
    int size = N * N;
    int bytes = size * sizeof(float);

    // 호스트 메모리 할당
    float *h_A = new float[size];
    float *h_B = new float[size];
    float *h_C = new float[size];
    float *h_C_CPU = new float[size];

    // 행렬 초기화
    for (int i = 0; i < size; i++) {
        h_A[i] = 1.0f;
        h_B[i] = 1.0f;
    }

    // GPU 메모리 할당
    float *d_A, *d_B, *d_C;
    cudaMalloc(&d_A, bytes);
    cudaMalloc(&d_B, bytes);
    cudaMalloc(&d_C, bytes);

    // 호스트에서 GPU로 데이터 복사
    cudaMemcpy(d_A, h_A, bytes, cudaMemcpyHostToDevice);
    cudaMemcpy(d_B, h_B, bytes, cudaMemcpyHostToDevice);

    // 블록과 그리드 차원 설정
    dim3 blockSize(16, 16);  // 16x16 스레드
    dim3 gridSize((N + blockSize.x - 1) / blockSize.x, (N + blockSize.y - 1) / blockSize.y);

    // GPU에서 행렬 곱 연산 및 시간 측정
    auto start_gpu = std::chrono::high_resolution_clock::now();
    matrixMultiply<<<gridSize, blockSize>>>(d_A, d_B, d_C, N);
    cudaDeviceSynchronize();  // 커널이 완료될 때까지 대기
    auto end_gpu = std::chrono::high_resolution_clock::now();
    std::chrono::duration<float, std::milli> gpu_duration = end_gpu - start_gpu;

    // GPU에서 호스트로 결과 복사
    cudaMemcpy(h_C, d_C, bytes, cudaMemcpyDeviceToHost);

    // CPU에서 행렬 곱 연산 및 시간 측정
    auto start_cpu = std::chrono::high_resolution_clock::now();
    matrixMultiplyCPU(h_A, h_B, h_C_CPU, N);
    auto end_cpu = std::chrono::high_resolution_clock::now();
    std::chrono::duration<float, std::milli> cpu_duration = end_cpu - start_cpu;

    // 결과 비교
    bool correct = true;
    for (int i = 0; i < size; i++) {
        if (abs(h_C[i] - h_C_CPU[i]) > 1e-4) {
            correct = false;
            break;
        }
    }

    // 결과 출력
    if (correct) {
        std::cout << "결과가 정확합니다." << std::endl;
    } else {
        std::cout << "결과가 정확하지 않습니다." << std::endl;
    }

    std::cout << "GPU time: " << gpu_duration.count() << " ms" << std::endl;
    std::cout << "CPU time: " << cpu_duration.count() << " ms" << std::endl;

    // 메모리 해제
    delete[] h_A;
    delete[] h_B;
    delete[] h_C;
    delete[] h_C_CPU;
    cudaFree(d_A);
    cudaFree(d_B);
    cudaFree(d_C);

    return 0;
}

행렬 크기 설정 : 512 X 512 행렬 곱셈 GPU 병렬 처리 효과를 확인하기 위해

커널 함수 matrixMultiply

CPU 함수

16 * 16 크기의 스레드 블록 사용,

 

행렬 곱 연산 시 더 빠른 속도 차이 확인 가능