์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ C++ ๋ณ‘๋ ฌ ์ปดํ“จํŒ…๊ณผ OpenMP, CUDA๋ฅผ ํ™œ์šฉํ•œ ๊ณ ์„ฑ๋Šฅ ๊ณ„์‚ฐ์˜ ๋ชจ๋“  ๊ฒƒ

๐Ÿš€ C++ ๋ณ‘๋ ฌ ์ปดํ“จํŒ…๊ณผ OpenMP, CUDA๋ฅผ ํ™œ์šฉํ•œ ๊ณ ์„ฑ๋Šฅ ๊ณ„์‚ฐ์˜ ๋ชจ๋“  ๊ฒƒ

์นœ๊ตฌ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ๋ฐฐ์šฐ๋Š” ๋ณ‘๋ ฌ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์„ธ๊ณ„ โšก

## ๐ŸŽฏ ์™œ ์ง€๊ธˆ ๋ณ‘๋ ฌ ์ปดํ“จํŒ…์„ ๋ฐฐ์›Œ์•ผ ํ• ๊นŒ? ์•ˆ๋…•! ์˜ค๋Š˜์€ ์ •๋ง ํฅ๋ฏธ์ง„์ง„ํ•œ ์ฃผ์ œ๋ฅผ ๊ฐ€์ง€๊ณ  ์™”์–ด. ๋ฐ”๋กœ **C++ ๋ณ‘๋ ฌ ์ปดํ“จํŒ…**์ด์•ผ. ํ˜น์‹œ ํ”„๋กœ๊ทธ๋žจ์ด ๋„ˆ๋ฌด ๋А๋ ค์„œ ๋‹ต๋‹ตํ–ˆ๋˜ ๊ฒฝํ—˜ ์žˆ์–ด? ๐ŸŒ

์š”์ฆ˜ ์„ธ์ƒ์€ ๋ฐ์ดํ„ฐ๊ฐ€ ํญ๋ฐœ์ ์œผ๋กœ ์ฆ๊ฐ€ํ•˜๊ณ  ์žˆ์ž–์•„. ์ธ๊ณต์ง€๋Šฅ, ๋น…๋ฐ์ดํ„ฐ ๋ถ„์„, ๊ฒŒ์ž„ ๊ทธ๋ž˜ํ”ฝ, ๊ณผํ•™ ์‹œ๋ฎฌ๋ ˆ์ด์…˜... ์ด ๋ชจ๋“  ๊ฒƒ๋“ค์ด ์—„์ฒญ๋‚œ ๊ณ„์‚ฐ ๋Šฅ๋ ฅ์„ ์š”๊ตฌํ•ด. ๊ทผ๋ฐ CPU ํด๋Ÿญ ์†๋„๋Š” ๋” ์ด์ƒ ์˜ˆ์ „์ฒ˜๋Ÿผ ๋น ๋ฅด๊ฒŒ ์ฆ๊ฐ€ํ•˜์ง€ ์•Š์•„. ๊ทธ๋ž˜์„œ ๋“ฑ์žฅํ•œ ๊ฒŒ ๋ฐ”๋กœ **๋ณ‘๋ ฌ ์ปดํ“จํŒ…**์ด์•ผ! ๐Ÿ’ช

๐Ÿ’ก ๋ณ‘๋ ฌ ์ปดํ“จํŒ…์ด๋ž€?

ํ•œ ๋ช…์ด 10์‹œ๊ฐ„ ๊ฑธ๋ฆด ์ผ์„ 10๋ช…์ด ๋‚˜๋ˆ ์„œ ํ•˜๋ฉด 1์‹œ๊ฐ„์— ๋๋‚ผ ์ˆ˜ ์žˆ์ž–์•„? ์ปดํ“จํ„ฐ๋„ ๋งˆ์ฐฌ๊ฐ€์ง€์•ผ. ํ•˜๋‚˜์˜ ์ž‘์—…์„ ์—ฌ๋Ÿฌ ๊ฐœ์˜ ์ž‘์€ ์ž‘์—…์œผ๋กœ ๋‚˜๋ˆ ์„œ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฑฐ์ง€. ์ด๊ฒŒ ๋ฐ”๋กœ ๋ณ‘๋ ฌ ์ปดํ“จํŒ…์˜ ํ•ต์‹ฌ์ด์•ผ!
ํŠนํžˆ C++๋Š” ์„ฑ๋Šฅ์ด ์ค‘์š”ํ•œ ๋ถ„์•ผ์—์„œ ์—ฌ์ „ํžˆ ์ตœ๊ณ ์˜ ์„ ํƒ์ด์•ผ. ๊ฒŒ์ž„ ์—”์ง„, ๊ธˆ์œต ์‹œ์Šคํ…œ, ๊ณผํ•™ ์—ฐ๊ตฌ... ์ด๋Ÿฐ ๊ณณ์—์„œ C++์˜ ๋ณ‘๋ ฌ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ํ•„์ˆ˜ ์Šคํ‚ฌ์ด ๋˜์—ˆ์–ด. ๊ทธ๋ฆฌ๊ณ  **์žฌ๋Šฅ๋„ท** ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ์ด๋Ÿฐ ๊ณ ๊ธ‰ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๊ธฐ์ˆ ์„ ๊ฐ€์ง„ ๊ฐœ๋ฐœ์ž๋“ค์˜ ์ˆ˜์š”๊ฐ€ ์ ์  ๋Š˜์–ด๋‚˜๊ณ  ์žˆ๋‹ค๊ณ  ํ•ด! ๐ŸŒŸ
## ๐Ÿ—๏ธ ๋ณ‘๋ ฌ ์ปดํ“จํŒ…์˜ ๊ธฐ์ดˆ ๊ฐœ๋… ๋ณธ๊ฒฉ์ ์œผ๋กœ ๋“ค์–ด๊ฐ€๊ธฐ ์ „์— ๊ธฐ๋ณธ ๊ฐœ๋…๋ถ€ํ„ฐ ํ™•์‹คํžˆ ์žก๊ณ  ๊ฐ€์ž!

### ๐Ÿ“Œ ๋™์‹œ์„ฑ vs ๋ณ‘๋ ฌ์„ฑ ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ํ—ท๊ฐˆ๋ คํ•˜๋Š” ๋ถ€๋ถ„์ธ๋ฐ, ์ด ๋‘˜์€ ์™„์ „ํžˆ ๋‹ค๋ฅธ ๊ฐœ๋…์ด์•ผ.

๐Ÿ”„ ๋™์‹œ์„ฑ (Concurrency)

์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋ฒˆ๊ฐˆ์•„๊ฐ€๋ฉฐ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ. ๋งˆ์น˜ ์š”๋ฆฌ์‚ฌ ํ•œ ๋ช…์ด ์—ฌ๋Ÿฌ ์š”๋ฆฌ๋ฅผ ๋™์‹œ์— ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ, ์‹ค์ œ๋กœ๋Š” ๋น ๋ฅด๊ฒŒ ์ „ํ™˜ํ•˜๋ฉด์„œ ์ฒ˜๋ฆฌํ•ด.

์˜ˆ์‹œ: ์‹ฑ๊ธ€ ์ฝ”์–ด CPU์—์„œ ๋ฉ€ํ‹ฐํƒœ์Šคํ‚น

โšก ๋ณ‘๋ ฌ์„ฑ (Parallelism)

์—ฌ๋Ÿฌ ์ž‘์—…์„ ์ •๋ง๋กœ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ. ์š”๋ฆฌ์‚ฌ ์—ฌ๋Ÿฌ ๋ช…์ด ๊ฐ์ž ๋‹ค๋ฅธ ์š”๋ฆฌ๋ฅผ ๋™์‹œ์— ๋งŒ๋“œ๋Š” ๊ฑฐ์ง€.

์˜ˆ์‹œ: ๋ฉ€ํ‹ฐ ์ฝ”์–ด CPU์—์„œ ์‹ค์ œ ๋™์‹œ ์‹คํ–‰
### ๐ŸŽญ ํ”„๋กœ์„ธ์Šค vs ์Šค๋ ˆ๋“œ
ํ”„๋กœ์„ธ์Šค (Process) ๐Ÿข
๋…๋ฆฝ์ ์ธ ์‹คํ–‰ ๋‹จ์œ„. ๊ฐ์ž์˜ ๋ฉ”๋ชจ๋ฆฌ ๊ณต๊ฐ„์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์„œ ์„œ๋กœ ๊ฐ„์„ญํ•˜์ง€ ์•Š์•„. ํ•˜์ง€๋งŒ ์ƒ์„ฑ ๋น„์šฉ์ด ํฌ๊ณ  ํ”„๋กœ์„ธ์Šค ๊ฐ„ ํ†ต์‹ ์ด ๋ณต์žกํ•ด.

์Šค๋ ˆ๋“œ (Thread) ๐Ÿ‘ฅ
ํ”„๋กœ์„ธ์Šค ๋‚ด๋ถ€์˜ ์‹คํ–‰ ๋‹จ์œ„. ๊ฐ™์€ ๋ฉ”๋ชจ๋ฆฌ ๊ณต๊ฐ„์„ ๊ณต์œ ํ•ด์„œ ๋ฐ์ดํ„ฐ ๊ตํ™˜์ด ์‰ฝ๊ณ  ์ƒ์„ฑ ๋น„์šฉ์ด ์ ์–ด. ํ•˜์ง€๋งŒ ๋™๊ธฐํ™” ๋ฌธ์ œ๋ฅผ ์กฐ์‹ฌํ•ด์•ผ ํ•ด!
๋ณ‘๋ ฌ ์ปดํ“จํŒ… ์•„ํ‚คํ…์ฒ˜ CPU ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ OpenMP ์ฝ”์–ด1 ์ฝ”์–ด2 ์ฝ”์–ด3 ์ฝ”์–ด4 GPU ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ CUDA ์ˆ˜์ฒœ ๊ฐœ์˜ ์ฝ”์–ด ์„ฑ๋Šฅ ๋น„๊ต CPU: ๋ฒ”์šฉ ์ž‘์—… GPU: ๋Œ€๊ทœ๋ชจ ๋ณ‘๋ ฌ ์ž‘์—… (์ตœ๋Œ€ 100๋ฐฐ ๋น ๋ฆ„) ์ž‘์—… ํŠน์„ฑ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๋„๊ตฌ๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ํ•ต์‹ฌ!
## ๐Ÿ”ง OpenMP: CPU ๋ณ‘๋ ฌ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ์‹œ์ž‘ ์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์ฝ”๋“œ๋ฅผ ๋งŒ์ ธ๋ณผ ์‹œ๊ฐ„์ด์•ผ! ๋จผ์ € OpenMP๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด์ž. ๐ŸŽ‰

### ๐ŸŒŸ OpenMP๊ฐ€ ๋ญ์•ผ? OpenMP๋Š” **Open Multi-Processing**์˜ ์•ฝ์ž๋กœ, C/C++/Fortran์—์„œ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ๋ณ‘๋ ฌ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” API์•ผ. ๊ฐ€์žฅ ํฐ ์žฅ์ ์€ ๊ธฐ์กด ์ฝ”๋“œ์— ๋ช‡ ์ค„๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ๋ณ‘๋ ฌํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ๊ฑฐ์ง€!

โœจ OpenMP์˜ ์žฅ์ 

โ€ข ๊ฐ„๋‹จํ•œ ์ง€์‹œ๋ฌธ(directive)์œผ๋กœ ๋ณ‘๋ ฌํ™” ๊ฐ€๋Šฅ
โ€ข ์ ์ง„์  ๋ณ‘๋ ฌํ™” ๊ฐ€๋Šฅ (ํ•œ ๋ฒˆ์— ์ „์ฒด๋ฅผ ๋ฐ”๊ฟ€ ํ•„์š” ์—†์Œ)
โ€ข ์ด์‹์„ฑ์ด ์ข‹์Œ (๋Œ€๋ถ€๋ถ„์˜ ์ปดํŒŒ์ผ๋Ÿฌ ์ง€์›)
โ€ข ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ ์‹œ์Šคํ…œ์— ์ตœ์ ํ™”
### ๐Ÿ’ป ์ฒซ ๋ฒˆ์งธ OpenMP ํ”„๋กœ๊ทธ๋žจ ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ์˜ˆ์ œ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด์ž!

#include <iostream>
#include <omp.h>

int main() {
    // ๋ณ‘๋ ฌ ์˜์—ญ ์‹œ์ž‘!
    #pragma omp parallel
    {
        int thread_id = omp_get_thread_num();
        int total_threads = omp_get_num_threads();
        
        std::cout << "์•ˆ๋…•! ๋‚˜๋Š” ์Šค๋ ˆ๋“œ " << thread_id 
                  << " / ์ „์ฒด " << total_threads << "๊ฐœ ์ค‘ ํ•˜๋‚˜์•ผ!" 
                  << std::endl;
    }
    
    return 0;
}
๐Ÿ” ์ฝ”๋“œ ๋ถ„์„

#pragma omp parallel: ์ด ํ•œ ์ค„์ด ๋งˆ๋ฒ•์„ ์ผ์œผ์ผœ! ์ด ์ง€์‹œ๋ฌธ ์•„๋ž˜์˜ ์ฝ”๋“œ ๋ธ”๋ก์ด ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์—์„œ ๋™์‹œ์— ์‹คํ–‰๋ผ.

omp_get_thread_num(): ํ˜„์žฌ ์Šค๋ ˆ๋“œ์˜ ID๋ฅผ ๋ฐ˜ํ™˜ํ•ด (0๋ถ€ํ„ฐ ์‹œ์ž‘)

omp_get_num_threads(): ์ „์ฒด ์Šค๋ ˆ๋“œ ๊ฐœ์ˆ˜๋ฅผ ์•Œ๋ ค์ค˜
### ๐Ÿ”„ ์‹ค์ „ ์˜ˆ์ œ: ๋ฐฐ์—ด ํ•ฉ๊ณ„ ๊ณ„์‚ฐ ์ด์ œ ์ข€ ๋” ์‹ค์šฉ์ ์ธ ์˜ˆ์ œ๋ฅผ ๋ด๋ณผ๊นŒ? ํฐ ๋ฐฐ์—ด์˜ ํ•ฉ๊ณ„๋ฅผ ๊ณ„์‚ฐํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์ด์•ผ.

#include <iostream>
#include <vector>
#include <omp.h>
#include <chrono>

// ์ˆœ์ฐจ ๋ฒ„์ „
double sequential_sum(const std::vector<double>& data) {
    double sum = 0.0;
    for (size_t i = 0; i < data.size(); i++) {
        sum += data[i];
    }
    return sum;
}

// OpenMP ๋ณ‘๋ ฌ ๋ฒ„์ „
double parallel_sum(const std::vector<double>& data) {
    double sum = 0.0;
    
    #pragma omp parallel for reduction(+:sum)
    for (size_t i = 0; i < data.size(); i++) {
        sum += data[i];
    }
    
    return sum;
}

int main() {
    const size_t N = 100000000; // 1์–ต ๊ฐœ์˜ ๋ฐ์ดํ„ฐ
    std::vector<double> data(N, 1.0);
    
    // ์ˆœ์ฐจ ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •
    auto start = std::chrono::high_resolution_clock::now();
    double result1 = sequential_sum(data);
    auto end = std::chrono::high_resolution_clock::now();
    auto duration1 = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "์ˆœ์ฐจ ์‹คํ–‰ ๊ฒฐ๊ณผ: " << result1 << std::endl;
    std::cout << "์ˆœ์ฐจ ์‹คํ–‰ ์‹œ๊ฐ„: " << duration1.count() << "ms" << std::endl;
    
    // ๋ณ‘๋ ฌ ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •
    start = std::chrono::high_resolution_clock::now();
    double result2 = parallel_sum(data);
    end = std::chrono::high_resolution_clock::now();
    auto duration2 = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "๋ณ‘๋ ฌ ์‹คํ–‰ ๊ฒฐ๊ณผ: " << result2 << std::endl;
    std::cout << "๋ณ‘๋ ฌ ์‹คํ–‰ ์‹œ๊ฐ„: " << duration2.count() << "ms" << std::endl;
    std::cout << "์†๋„ ํ–ฅ์ƒ: " << (double)duration1.count() / duration2.count() << "๋ฐฐ!" << std::endl;
    
    return 0;
}
โš ๏ธ reduction ์ ˆ์ด ์ค‘์š”ํ•ด!

reduction(+:sum)์€ ๊ฐ ์Šค๋ ˆ๋“œ๊ฐ€ ์ž์‹ ๋งŒ์˜ sum ๋ณ€์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ๊ณ„์‚ฐํ•œ ํ›„, ๋งˆ์ง€๋ง‰์— ๋ชจ๋“  ๊ฒฐ๊ณผ๋ฅผ ํ•ฉ์ณ์ฃผ๋Š” ์—ญํ• ์„ ํ•ด. ์ด๊ฒŒ ์—†์œผ๋ฉด ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ ๋ณ€์ˆ˜๋ฅผ ์ˆ˜์ •ํ•˜๋ ค๊ณ  ํ•ด์„œ ๊ฒฐ๊ณผ๊ฐ€ ์—‰๋ง์ด ๋  ์ˆ˜ ์žˆ์–ด! ๐Ÿ˜ฑ
### ๐Ÿ“Š OpenMP์˜ ์ฃผ์š” ์ง€์‹œ๋ฌธ๋“ค OpenMP์—๋Š” ๋‹ค์–‘ํ•œ ์ง€์‹œ๋ฌธ๋“ค์ด ์žˆ์–ด. ์ž์ฃผ ์“ฐ์ด๋Š” ๊ฒƒ๋“ค์„ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ!

์ง€์‹œ๋ฌธ ์„ค๋ช… ์‚ฌ์šฉ ์˜ˆ์‹œ
parallel ๋ณ‘๋ ฌ ์˜์—ญ ์ƒ์„ฑ #pragma omp parallel
for ๋ฐ˜๋ณต๋ฌธ ๋ณ‘๋ ฌํ™” #pragma omp parallel for
sections ์„œ๋กœ ๋‹ค๋ฅธ ์ž‘์—…์„ ๋ณ‘๋ ฌ ์‹คํ–‰ #pragma omp sections
critical ํ•œ ๋ฒˆ์— ํ•œ ์Šค๋ ˆ๋“œ๋งŒ ์‹คํ–‰ #pragma omp critical
atomic ์›์ž์  ์—ฐ์‚ฐ ๋ณด์žฅ #pragma omp atomic
barrier ๋ชจ๋“  ์Šค๋ ˆ๋“œ ๋™๊ธฐํ™” #pragma omp barrier
reduction ๊ฒฐ๊ณผ ํ•ฉ์‚ฐ reduction(+:sum)
### ๐ŸŽจ ์‹ค์ „ ํ™œ์šฉ: ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ๋Š” ๋ณ‘๋ ฌํ™”์˜ ์™„๋ฒฝํ•œ ์˜ˆ์‹œ์•ผ! ๊ฐ ํ”ฝ์…€์„ ๋…๋ฆฝ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ .

#include <vector>
#include <omp.h>

struct Pixel {
    unsigned char r, g, b;
};

class Image {
private:
    std::vector<Pixel> data;
    int width, height;
    
public:
    Image(int w, int h) : width(w), height(h), data(w * h) {}
    
    // ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ ๋ณ€ํ™˜ (๋ณ‘๋ ฌ ๋ฒ„์ „)
    void convertToGrayscale() {
        #pragma omp parallel for collapse(2)
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int idx = y * width + x;
                Pixel& p = data[idx];
                
                // ๊ทธ๋ ˆ์ด์Šค์ผ€์ผ ๊ณต์‹: 0.299R + 0.587G + 0.114B
                unsigned char gray = static_cast<unsigned char>(
                    0.299 * p.r + 0.587 * p.g + 0.114 * p.b
                );
                
                p.r = p.g = p.b = gray;
            }
        }
    }
    
    // ๋ธ”๋Ÿฌ ํšจ๊ณผ (๋ณ‘๋ ฌ ๋ฒ„์ „)
    void applyBlur(int radius) {
        std::vector<Pixel> temp = data;
        
        #pragma omp parallel for collapse(2)
        for (int y = radius; y < height - radius; y++) {
            for (int x = radius; x < width - radius; x++) {
                int sumR = 0, sumG = 0, sumB = 0;
                int count = 0;
                
                // ์ฃผ๋ณ€ ํ”ฝ์…€ ํ‰๊ท  ๊ณ„์‚ฐ
                for (int dy = -radius; dy <= radius; dy++) {
                    for (int dx = -radius; dx <= radius; dx++) {
                        int idx = (y + dy) * width + (x + dx);
                        sumR += temp[idx].r;
                        sumG += temp[idx].g;
                        sumB += temp[idx].b;
                        count++;
                    }
                }
                
                int idx = y * width + x;
                data[idx].r = sumR / count;
                data[idx].g = sumG / count;
                data[idx].b = sumB / count;
            }
        }
    }
};
๐Ÿ’ก collapse(2)์˜ ์˜๋ฏธ

collapse(2)๋Š” ์ค‘์ฒฉ๋œ 2๊ฐœ์˜ ๋ฐ˜๋ณต๋ฌธ์„ ํ•˜๋‚˜์˜ ํฐ ๋ฐ˜๋ณต๋ฌธ์œผ๋กœ ํ•ฉ์ณ์„œ ๋ณ‘๋ ฌํ™”ํ•ด. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋” ๋งŽ์€ ์ž‘์—…์„ ์Šค๋ ˆ๋“œ๋“ค์—๊ฒŒ ๊ณ ๋ฅด๊ฒŒ ๋ถ„๋ฐฐํ•  ์ˆ˜ ์žˆ์–ด์„œ ํšจ์œจ์ด ์ข‹์•„์ ธ! ๐Ÿš€
## ๐ŸŽฎ CUDA: GPU์˜ ๊ฐ•๋ ฅํ•œ ํž˜์„ ํ™œ์šฉํ•˜์ž! ์ž, ์ด์ œ ์ง„์งœ ๊ฐ•๋ ฅํ•œ ๋ฌด๊ธฐ๋ฅผ ๊บผ๋‚ผ ์‹œ๊ฐ„์ด์•ผ! CUDA๋Š” NVIDIA GPU๋ฅผ ํ™œ์šฉํ•œ ๋ณ‘๋ ฌ ์ปดํ“จํŒ… ํ”Œ๋žซํผ์ด์•ผ. CPU๊ฐ€ ๋ช‡ ๊ฐœ์—์„œ ์ˆ˜์‹ญ ๊ฐœ์˜ ์ฝ”์–ด๋ฅผ ๊ฐ€์ง„๋‹ค๋ฉด, GPU๋Š” **์ˆ˜์ฒœ ๊ฐœ์˜ ์ฝ”์–ด**๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์–ด! ๐Ÿคฏ

### ๐ŸŒˆ CUDA๊ฐ€ ํŠน๋ณ„ํ•œ ์ด์œ 
GPU vs CPU: ์„ค๊ณ„ ์ฒ ํ•™์˜ ์ฐจ์ด

CPU ๐Ÿง : ๋ณต์žกํ•œ ์ž‘์—…์„ ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋„๋ก ์„ค๊ณ„๋จ. ๊ฐ ์ฝ”์–ด๊ฐ€ ๋งค์šฐ ๊ฐ•๋ ฅํ•˜๊ณ  ๋…๋ฆฝ์ ์œผ๋กœ ๋‹ค์–‘ํ•œ ์ž‘์—… ์ˆ˜ํ–‰ ๊ฐ€๋Šฅ.

GPU ๐Ÿ’ช: ๋‹จ์ˆœํ•œ ์ž‘์—…์„ ๋Œ€๋Ÿ‰์œผ๋กœ ๋™์‹œ์— ์ฒ˜๋ฆฌํ•˜๋„๋ก ์„ค๊ณ„๋จ. ์ˆ˜์ฒœ ๊ฐœ์˜ ์ž‘์€ ์ฝ”์–ด๊ฐ€ ๊ฐ™์€ ์ž‘์—…์„ ๋™์‹œ์— ์ˆ˜ํ–‰.
GPU๋Š” ์›๋ž˜ ๊ทธ๋ž˜ํ”ฝ ๋ Œ๋”๋ง์„ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ์–ด. ํ™”๋ฉด์˜ ์ˆ˜๋ฐฑ๋งŒ ๊ฐœ ํ”ฝ์…€์„ ๋™์‹œ์— ๊ณ„์‚ฐํ•ด์•ผ ํ•˜๋‹ˆ๊นŒ! ๊ทธ๋Ÿฐ๋ฐ ์ด ๊ตฌ์กฐ๊ฐ€ ๊ณผํ•™ ๊ณ„์‚ฐ, ๋”ฅ๋Ÿฌ๋‹, ์•”ํ˜ธํ™”ํ ์ฑ„๊ตด ๋“ฑ ๋‹ค์–‘ํ•œ ๋ถ„์•ผ์— ์™„๋ฒฝํ•˜๊ฒŒ ๋งž์•„๋–จ์–ด์ง„ ๊ฑฐ์ง€.

### ๐Ÿ—๏ธ CUDA ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ชจ๋ธ CUDA ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ํ•ต์‹ฌ ๊ฐœ๋…๋“ค์„ ์•Œ์•„๋ณด์ž!

๐Ÿ  Host (ํ˜ธ์ŠคํŠธ)

CPU์™€ ์‹œ์Šคํ…œ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์˜๋ฏธํ•ด. ์ผ๋ฐ˜์ ์ธ C++ ์ฝ”๋“œ๊ฐ€ ์‹คํ–‰๋˜๋Š” ๊ณณ์ด์•ผ.

๐ŸŽฏ Device (๋””๋ฐ”์ด์Šค)

GPU์™€ GPU ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ์˜๋ฏธํ•ด. CUDA ์ปค๋„์ด ์‹คํ–‰๋˜๋Š” ๊ณณ์ด์ง€.

โš™๏ธ Kernel (์ปค๋„)

GPU์—์„œ ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜์•ผ. ์ˆ˜์ฒœ ๊ฐœ์˜ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ์‹คํ–‰ํ•ด!

๐Ÿงต Thread (์Šค๋ ˆ๋“œ)

GPU์—์„œ ์‹คํ–‰๋˜๋Š” ๊ฐ€์žฅ ์ž‘์€ ๋‹จ์œ„. ์ˆ˜์ฒœ~์ˆ˜๋งŒ ๊ฐœ๊ฐ€ ๋™์‹œ์— ์‹คํ–‰๋ผ!
### ๐Ÿ’ป ์ฒซ ๋ฒˆ์งธ CUDA ํ”„๋กœ๊ทธ๋žจ ์ „ํ†ต์ ์ธ "Hello World"๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด์ž!

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

// __global__์€ ์ด ํ•จ์ˆ˜๊ฐ€ GPU์—์„œ ์‹คํ–‰๋จ์„ ์˜๋ฏธํ•ด
__global__ void helloFromGPU() {
    int threadId = threadIdx.x + blockIdx.x * blockDim.x;
    printf("์•ˆ๋…•! ๋‚˜๋Š” GPU ์Šค๋ ˆ๋“œ %d์•ผ!\n", threadId);
}

int main() {
    std::cout << "CPU์—์„œ ์‹œ์ž‘!" << std::endl;
    
    // 10๊ฐœ์˜ ๋ธ”๋ก, ๊ฐ ๋ธ”๋ก๋‹น 10๊ฐœ์˜ ์Šค๋ ˆ๋“œ = ์ด 100๊ฐœ ์Šค๋ ˆ๋“œ
    helloFromGPU<<<10, 10>>>();
    
    // GPU ์ž‘์—…์ด ๋๋‚  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
    cudaDeviceSynchronize();
    
    std::cout << "CPU์—์„œ ์ข…๋ฃŒ!" << std::endl;
    return 0;
}
๐Ÿ” CUDA ๋ฌธ๋ฒ• ํ•ด์„ค

__global__: GPU์—์„œ ์‹คํ–‰๋˜๋Š” ์ปค๋„ ํ•จ์ˆ˜ ์„ ์–ธ

<<<๋ธ”๋ก ์ˆ˜, ์Šค๋ ˆ๋“œ ์ˆ˜>>>: ์ปค๋„ ์‹คํ–‰ ์„ค์ •. ์ด๊ฒŒ CUDA์˜ ์‹œ๊ทธ๋‹ˆ์ฒ˜์•ผ!

threadIdx.x: ๋ธ”๋ก ๋‚ด์—์„œ์˜ ์Šค๋ ˆ๋“œ ์ธ๋ฑ์Šค

blockIdx.x: ๊ทธ๋ฆฌ๋“œ ๋‚ด์—์„œ์˜ ๋ธ”๋ก ์ธ๋ฑ์Šค

blockDim.x: ๋ธ”๋ก๋‹น ์Šค๋ ˆ๋“œ ๊ฐœ์ˆ˜
### ๐Ÿš€ ์‹ค์ „ ์˜ˆ์ œ: ๋ฒกํ„ฐ ๋ง์…ˆ ์ด์ œ ์‹ค์ œ๋กœ ๊ณ„์‚ฐ์„ ํ•ด๋ณด์ž! ๋‘ ๊ฐœ์˜ ํฐ ๋ฐฐ์—ด์„ ๋”ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์ด์•ผ.

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

// CPU ๋ฒ„์ „
void vectorAddCPU(const float* a, const float* b, float* c, int n) {
    for (int i = 0; i < n; i++) {
        c[i] = a[i] + b[i];
    }
}

// GPU ์ปค๋„
__global__ void vectorAddGPU(const float* a, const float* b, float* c, int n) {
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    
    if (idx < n) {
        c[idx] = a[idx] + b[idx];
    }
}

int main() {
    const int N = 10000000; // 1์ฒœ๋งŒ ๊ฐœ์˜ ์›์†Œ
    const int bytes = N * sizeof(float);
    
    // ํ˜ธ์ŠคํŠธ ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น
    float *h_a = new float[N];
    float *h_b = new float[N];
    float *h_c = new float[N];
    
    // ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”
    for (int i = 0; i < N; i++) {
        h_a[i] = static_cast<float>(i);
        h_b[i] = static_cast<float>(i * 2);
    }
    
    // CPU ๋ฒ„์ „ ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ •
    auto start = std::chrono::high_resolution_clock::now();
    vectorAddCPU(h_a, h_b, h_c, N);
    auto end = std::chrono::high_resolution_clock::now();
    auto cpuTime = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "CPU ์‹คํ–‰ ์‹œ๊ฐ„: " << cpuTime.count() << "ms" << std::endl;
    
    // ๋””๋ฐ”์ด์Šค ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น
    float *d_a, *d_b, *d_c;
    cudaMalloc(&d_a, bytes);
    cudaMalloc(&d_b, bytes);
    cudaMalloc(&d_c, bytes);
    
    // GPU ๋ฒ„์ „ ์‹คํ–‰ ์‹œ๊ฐ„ ์ธก์ • (๋ฉ”๋ชจ๋ฆฌ ๋ณต์‚ฌ ํฌํ•จ)
    start = std::chrono::high_resolution_clock::now();
    
    // ํ˜ธ์ŠคํŠธ โ†’ ๋””๋ฐ”์ด์Šค ๋ฉ”๋ชจ๋ฆฌ ๋ณต์‚ฌ
    cudaMemcpy(d_a, h_a, bytes, cudaMemcpyHostToDevice);
    cudaMemcpy(d_b, h_b, bytes, cudaMemcpyHostToDevice);
    
    // ์ปค๋„ ์‹คํ–‰ ์„ค์ •
    int threadsPerBlock = 256;
    int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
    
    vectorAddGPU<<<blocksPerGrid, threadsPerBlock>>>(d_a, d_b, d_c, N);
    
    // ๋””๋ฐ”์ด์Šค โ†’ ํ˜ธ์ŠคํŠธ ๋ฉ”๋ชจ๋ฆฌ ๋ณต์‚ฌ
    cudaMemcpy(h_c, d_c, bytes, cudaMemcpyDeviceToHost);
    
    end = std::chrono::high_resolution_clock::now();
    auto gpuTime = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
    
    std::cout << "GPU ์‹คํ–‰ ์‹œ๊ฐ„ (๋ฉ”๋ชจ๋ฆฌ ๋ณต์‚ฌ ํฌํ•จ): " << gpuTime.count() << "ms" << std::endl;
    std::cout << "์†๋„ ํ–ฅ์ƒ: " << (double)cpuTime.count() / gpuTime.count() << "๋ฐฐ!" << std::endl;
    
    // ๊ฒฐ๊ณผ ๊ฒ€์ฆ
    bool correct = true;
    for (int i = 0; i < N; i++) {
        if (abs(h_c[i] - (h_a[i] + h_b[i])) > 1e-5) {
            correct = false;
            break;
        }
    }
    std::cout << "๊ฒฐ๊ณผ ๊ฒ€์ฆ: " << (correct ? "์„ฑ๊ณต! โœ…" : "์‹คํŒจ! โŒ") << std::endl;
    
    // ๋ฉ”๋ชจ๋ฆฌ ํ•ด์ œ
    delete[] h_a;
    delete[] h_b;
    delete[] h_c;
    cudaFree(d_a);
    cudaFree(d_b);
    cudaFree(d_c);
    
    return 0;
}
โš ๏ธ ๋ฉ”๋ชจ๋ฆฌ ๋ณต์‚ฌ ์˜ค๋ฒ„ํ—ค๋“œ

GPU ํ”„๋กœ๊ทธ๋ž˜๋ฐ์—์„œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ ๋ฉ”๋ชจ๋ฆฌ ๋ณต์‚ฌ์•ผ! CPU์™€ GPU๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ๋ฉ”๋ชจ๋ฆฌ ๊ณต๊ฐ„์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š” ๋ฐ ์‹œ๊ฐ„์ด ๊ฑธ๋ ค. ๊ทธ๋ž˜์„œ ์ž‘์€ ๋ฐ์ดํ„ฐ๋Š” ์˜คํžˆ๋ ค CPU๊ฐ€ ๋” ๋น ๋ฅผ ์ˆ˜ ์žˆ์–ด. GPU์˜ ์ง„๊ฐ€๋Š” **๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ** ๋ฐœํœ˜๋ผ! ๐Ÿ’ก
### ๐ŸŽฏ CUDA ๋ฉ”๋ชจ๋ฆฌ ๊ณ„์ธต ๊ตฌ์กฐ CUDA์˜ ์„ฑ๋Šฅ์„ ์ตœ๋Œ€๋กœ ๋Œ์–ด๋‚ด๋ ค๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•ด์•ผ ํ•ด!

๋ฉ”๋ชจ๋ฆฌ ์ข…๋ฅ˜ ๋ฒ”์œ„ ์†๋„ ํฌ๊ธฐ ์šฉ๋„
Register ์Šค๋ ˆ๋“œ โšกโšกโšกโšกโšก ๋งค์šฐ ์ž‘์Œ ์ง€์—ญ ๋ณ€์ˆ˜
Shared Memory ๋ธ”๋ก โšกโšกโšกโšก ์ž‘์Œ (~48KB) ๋ธ”๋ก ๋‚ด ๊ณต์œ  ๋ฐ์ดํ„ฐ
Local Memory ์Šค๋ ˆ๋“œ โšกโšก ์ค‘๊ฐ„ ๋ ˆ์ง€์Šคํ„ฐ ์˜ค๋ฒ„ํ”Œ๋กœ์šฐ
Global Memory ์ „์ฒด โšก ํผ (์ˆ˜GB) ์ฃผ ๋ฉ”๋ชจ๋ฆฌ
Constant Memory ์ „์ฒด โšกโšกโšก ์ž‘์Œ (64KB) ์ฝ๊ธฐ ์ „์šฉ ์ƒ์ˆ˜
Texture Memory ์ „์ฒด โšกโšกโšก ํผ ๊ณต๊ฐ„ ์ง€์—ญ์„ฑ ๋ฐ์ดํ„ฐ
### ๐Ÿ”ฅ ์ตœ์ ํ™” ๊ธฐ๋ฒ•: Shared Memory ํ™œ์šฉ Shared Memory๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์–ด! ํ–‰๋ ฌ ๊ณฑ์…ˆ ์˜ˆ์ œ๋ฅผ ๋ด๋ณด์ž.

#include <cuda_runtime.h>

#define TILE_SIZE 16

// ๊ธฐ๋ณธ ๋ฒ„์ „ (๋А๋ฆผ)
__global__ void matrixMulBasic(float* A, 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.0f;
        for (int k = 0; k < N; k++) {
            sum += A[row * N + k] * B[k * N + col];
        }
        C[row * N + col] = sum;
    }
}

// ์ตœ์ ํ™” ๋ฒ„์ „ (๋น ๋ฆ„!) - Shared Memory ์‚ฌ์šฉ
__global__ void matrixMulOptimized(float* A, float* B, float* C, int N) {
    // Shared Memory ์„ ์–ธ
    __shared__ float tileA[TILE_SIZE][TILE_SIZE];
    __shared__ float tileB[TILE_SIZE][TILE_SIZE];
    
    int row = blockIdx.y * TILE_SIZE + threadIdx.y;
    int col = blockIdx.x * TILE_SIZE + threadIdx.x;
    
    float sum = 0.0f;
    
    // ํƒ€์ผ ๋‹จ์œ„๋กœ ์ฒ˜๋ฆฌ
    for (int t = 0; t < (N + TILE_SIZE - 1) / TILE_SIZE; t++) {
        // Global Memory โ†’ Shared Memory๋กœ ๋ฐ์ดํ„ฐ ๋กœ๋“œ
        if (row < N && t * TILE_SIZE + threadIdx.x < N) {
            tileA[threadIdx.y][threadIdx.x] = A[row * N + t * TILE_SIZE + threadIdx.x];
        } else {
            tileA[threadIdx.y][threadIdx.x] = 0.0f;
        }
        
        if (col < N && t * TILE_SIZE + threadIdx.y < N) {
            tileB[threadIdx.y][threadIdx.x] = B[(t * TILE_SIZE + threadIdx.y) * N + col];
        } else {
            tileB[threadIdx.y][threadIdx.x] = 0.0f;
        }
        
        // ๋ชจ๋“  ์Šค๋ ˆ๋“œ๊ฐ€ ๋ฐ์ดํ„ฐ ๋กœ๋“œ๋ฅผ ์™„๋ฃŒํ•  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
        __syncthreads();
        
        // Shared Memory์—์„œ ๊ณ„์‚ฐ (๋น ๋ฆ„!)
        for (int k = 0; k < TILE_SIZE; k++) {
            sum += tileA[threadIdx.y][k] * tileB[k][threadIdx.x];
        }
        
        // ๋‹ค์Œ ํƒ€์ผ๋กœ ๋„˜์–ด๊ฐ€๊ธฐ ์ „ ๋™๊ธฐํ™”
        __syncthreads();
    }
    
    if (row < N && col < N) {
        C[row * N + col] = sum;
    }
}
โœจ ์™œ Shared Memory๊ฐ€ ๋น ๋ฅผ๊นŒ?

Global Memory๋Š” GPU ์นฉ ์™ธ๋ถ€์— ์žˆ์–ด์„œ ์ ‘๊ทผ์ด ๋А๋ ค (์ˆ˜๋ฐฑ ์‚ฌ์ดํด). ๋ฐ˜๋ฉด Shared Memory๋Š” ์นฉ ๋‚ด๋ถ€์— ์žˆ์–ด์„œ ์ ‘๊ทผ์ด ๋งค์šฐ ๋น ๋ฅด์ง€ (์ˆ˜ ์‚ฌ์ดํด). ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ ๋กœ๋“œํ•ด์„œ ์—ฌ๋Ÿฌ ๋ฒˆ ์‚ฌ์šฉํ•  ๋•Œ Shared Memory๋ฅผ ์“ฐ๋ฉด ์—„์ฒญ๋‚œ ์†๋„ ํ–ฅ์ƒ์„ ์–ป์„ ์ˆ˜ ์žˆ์–ด! ๐Ÿš€
CUDA ๋ฉ”๋ชจ๋ฆฌ ๊ณ„์ธต๊ณผ ์„ฑ๋Šฅ ๋ ˆ์ง€์Šคํ„ฐ 1 ์‚ฌ์ดํด Shared Memory ์ˆ˜ ์‚ฌ์ดํด L1/L2 ์บ์‹œ ์ˆ˜์‹ญ ์‚ฌ์ดํด Global Memory ์ˆ˜๋ฐฑ ์‚ฌ์ดํด ์†๋„ ๋น„๊ต ๋ ˆ์ง€์Šคํ„ฐ: 100๋ฐฐ ๋น ๋ฆ„ Shared: 50๋ฐฐ ๋น ๋ฆ„ ์บ์‹œ: 10๋ฐฐ ๋น ๋ฆ„ Global: ๊ธฐ์ค€ ์ตœ์ ํ™” ํŒ ์ž์ฃผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ดํ„ฐ๋Š” Shared Memory์—! Global Memory ์ ‘๊ทผ์€ ์ตœ์†Œํ™”! ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ ํŒจํ„ด์„ ์—ฐ์†์ ์œผ๋กœ! Bank Conflict๋ฅผ ํ”ผํ•˜์ž!
## ๐ŸŽช OpenMP vs CUDA: ์–ธ์ œ ๋ฌด์—‡์„ ์จ์•ผ ํ• ๊นŒ? ์ž, ์ด์ œ ๋‘ ๊ธฐ์ˆ ์„ ๋‹ค ๋ฐฐ์› ์œผ๋‹ˆ ์–ธ์ œ ์–ด๋–ค ๊ฑธ ์จ์•ผ ํ• ์ง€ ์•Œ์•„๋ณด์ž! ๐Ÿค”

### ๐Ÿ“Š ๋น„๊ตํ‘œ
ํŠน์„ฑ OpenMP CUDA
ํ•˜๋“œ์›จ์–ด CPU (๋ฉ€ํ‹ฐ์ฝ”์–ด) NVIDIA GPU
๋ณ‘๋ ฌํ™” ์ˆ˜์ค€ ์ˆ˜์‹ญ ๊ฐœ ์Šค๋ ˆ๋“œ ์ˆ˜์ฒœ~์ˆ˜๋งŒ ๊ฐœ ์Šค๋ ˆ๋“œ
ํ•™์Šต ๋‚œ์ด๋„ ์‰ฌ์›€ โญโญ ์–ด๋ ค์›€ โญโญโญโญ
์ฝ”๋“œ ์ˆ˜์ • ์ตœ์†Œํ•œ (์ง€์‹œ๋ฌธ๋งŒ ์ถ”๊ฐ€) ๋งŽ์Œ (์ปค๋„ ์ž‘์„ฑ ํ•„์š”)
๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ์ž๋™ (๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ) ์ˆ˜๋™ (๋ช…์‹œ์  ๋ณต์‚ฌ)
์ ํ•ฉํ•œ ์ž‘์—… ๋ถˆ๊ทœ์น™ํ•œ ๋ณ‘๋ ฌ์„ฑ ๊ทœ์น™์ ์ธ ๋Œ€๋Ÿ‰ ๋ณ‘๋ ฌ์„ฑ
์ด์‹์„ฑ ๋†’์Œ (๋ชจ๋“  CPU) ๋‚ฎ์Œ (NVIDIA GPU๋งŒ)
์„ฑ๋Šฅ ์ข‹์Œ ๋งค์šฐ ์ข‹์Œ (์ ํ•ฉํ•œ ์ž‘์—…์—์„œ)
### ๐ŸŽฏ ์„ ํƒ ๊ฐ€์ด๋“œ

๐Ÿ”ต OpenMP๋ฅผ ์„ ํƒํ•ด์•ผ ํ•  ๋•Œ

โ€ข ๊ธฐ์กด ์ฝ”๋“œ๋ฅผ ๋น ๋ฅด๊ฒŒ ๋ณ‘๋ ฌํ™”ํ•˜๊ณ  ์‹ถ์„ ๋•Œ
โ€ข ์ž‘์—…์ด ๋ถˆ๊ทœ์น™ํ•˜๊ฑฐ๋‚˜ ์กฐ๊ฑด๋ถ€ ๋ถ„๊ธฐ๊ฐ€ ๋งŽ์„ ๋•Œ
โ€ข GPU๊ฐ€ ์—†๊ฑฐ๋‚˜ ์ด์‹์„ฑ์ด ์ค‘์š”ํ•  ๋•Œ
โ€ข ๋ฐ์ดํ„ฐ ํฌ๊ธฐ๊ฐ€ ์ž‘๊ฑฐ๋‚˜ ์ค‘๊ฐ„ ์ •๋„์ผ ๋•Œ
โ€ข ๋น ๋ฅธ ํ”„๋กœํ† ํƒ€์ดํ•‘์ด ํ•„์š”ํ•  ๋•Œ

์˜ˆ์‹œ: ํŒŒ์ผ ์ฒ˜๋ฆฌ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ, ์›น ์„œ๋ฒ„

๐ŸŸข CUDA๋ฅผ ์„ ํƒํ•ด์•ผ ํ•  ๋•Œ

โ€ข ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•  ๋•Œ
โ€ข ์ž‘์—…์ด ๊ทœ์น™์ ์ด๊ณ  ๋ฐ˜๋ณต์ ์ผ ๋•Œ
โ€ข ์ตœ๊ณ ์˜ ์„ฑ๋Šฅ์ด ํ•„์š”ํ•  ๋•Œ
โ€ข ํ–‰๋ ฌ ์—ฐ์‚ฐ์ด๋‚˜ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ๊ฐ™์€ ์ž‘์—…
โ€ข ๋”ฅ๋Ÿฌ๋‹, ๊ณผํ•™ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ๋“ฑ

์˜ˆ์‹œ: ๋”ฅ๋Ÿฌ๋‹, ์•”ํ˜ธํ™”ํ ์ฑ„๊ตด, ์˜์ƒ ์ฒ˜๋ฆฌ
### ๐Ÿค ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ ์‚ฌ์‹ค ๊ฐ€์žฅ ์ข‹์€ ๋ฐฉ๋ฒ•์€ ๋‘˜ ๋‹ค ์‚ฌ์šฉํ•˜๋Š” ๊ฑฐ์•ผ! CPU์™€ GPU๋ฅผ ๋™์‹œ์— ํ™œ์šฉํ•˜๋ฉด ์ตœ๊ณ ์˜ ์„ฑ๋Šฅ์„ ์–ป์„ ์ˆ˜ ์žˆ์–ด.

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

// GPU ์ปค๋„
__global__ void processChunkGPU(float* data, int size) {
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    if (idx < size) {
        // GPU์—์„œ ์ฒ˜๋ฆฌํ•  ์ž‘์—…
        data[idx] = data[idx] * data[idx];
    }
}

// CPU ํ•จ์ˆ˜
void processChunkCPU(float* data, int size) {
    #pragma omp parallel for
    for (int i = 0; i < size; i++) {
        // CPU์—์„œ ์ฒ˜๋ฆฌํ•  ์ž‘์—…
        data[i] = data[i] * data[i];
    }
}

int main() {
    const int TOTAL_SIZE = 10000000;
    const int GPU_PORTION = 8000000; // 80%๋Š” GPU์—์„œ
    const int CPU_PORTION = 2000000; // 20%๋Š” CPU์—์„œ
    
    float *data = new float[TOTAL_SIZE];
    float *d_data;
    
    // ๋ฐ์ดํ„ฐ ์ดˆ๊ธฐํ™”
    for (int i = 0; i < TOTAL_SIZE; i++) {
        data[i] = static_cast<float>(i);
    }
    
    // GPU ๋ฉ”๋ชจ๋ฆฌ ํ• ๋‹น
    cudaMalloc(&d_data, GPU_PORTION * sizeof(float));
    
    // CPU์™€ GPU ๋™์‹œ ์‹คํ–‰!
    #pragma omp parallel sections
    {
        #pragma omp section
        {
            // GPU ์ž‘์—…
            cudaMemcpy(d_data, data, GPU_PORTION * sizeof(float), 
                      cudaMemcpyHostToDevice);
            
            int threadsPerBlock = 256;
            int blocksPerGrid = (GPU_PORTION + threadsPerBlock - 1) / threadsPerBlock;
            processChunkGPU<<<blocksPerGrid, threadsPerBlock>>>(d_data, GPU_PORTION);
            
            cudaMemcpy(data, d_data, GPU_PORTION * sizeof(float), 
                      cudaMemcpyDeviceToHost);
        }
        
        #pragma omp section
        {
            // CPU ์ž‘์—…
            processChunkCPU(data + GPU_PORTION, CPU_PORTION);
        }
    }
    
    std::cout << "CPU์™€ GPU๊ฐ€ ํ•จ๊ป˜ ์ž‘์—… ์™„๋ฃŒ! ๐ŸŽ‰" << std::endl;
    
    // ์ •๋ฆฌ
    delete[] data;
    cudaFree(d_data);
    
    return 0;
}
๐Ÿ’ก ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ ‘๊ทผ๋ฒ•์˜ ์žฅ์ 

CPU์™€ GPU๋ฅผ ๋™์‹œ์— ํ™œ์šฉํ•˜๋ฉด ๊ฐ๊ฐ์˜ ์žฅ์ ์„ ์‚ด๋ฆด ์ˆ˜ ์žˆ์–ด! GPU๊ฐ€ ๋Œ€๋Ÿ‰์˜ ๊ทœ์น™์ ์ธ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๋™์•ˆ, CPU๋Š” ๋ถˆ๊ทœ์น™ํ•œ ์ž‘์—…์ด๋‚˜ ์ œ์–ด ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋Š” ๊ฑฐ์ง€. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ „์ฒด ์‹œ์Šคํ…œ์˜ ํ™œ์šฉ๋„๋ฅผ ์ตœ๋Œ€ํ™”ํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿš€
## ๐Ÿ› ๏ธ ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ์ด๋ฏธ์ง€ ํ•„ํ„ฐ๋ง ์‹œ์Šคํ…œ ์ด์ œ ๋ฐฐ์šด ๊ฑธ ์ข…ํ•ฉํ•ด์„œ ์‹ค์ „ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž! ๋Œ€์šฉ๋Ÿ‰ ์ด๋ฏธ์ง€์— ๋‹ค์–‘ํ•œ ํ•„ํ„ฐ๋ฅผ ์ ์šฉํ•˜๋Š” ์‹œ์Šคํ…œ์ด์•ผ. ๐Ÿ“ธ

### ๐ŸŽจ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ
์‹œ์Šคํ…œ ๊ตฌ์„ฑ

1. ์ด๋ฏธ์ง€ ๋กœ๋”ฉ: CPU์—์„œ ํŒŒ์ผ ์ฝ๊ธฐ
2. ์ „์ฒ˜๋ฆฌ: OpenMP๋กœ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ
3. ํ•„ํ„ฐ ์ ์šฉ: CUDA๋กœ GPU ๊ฐ€์†
4. ํ›„์ฒ˜๋ฆฌ: OpenMP๋กœ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ
5. ์ €์žฅ: CPU์—์„œ ํŒŒ์ผ ์“ฐ๊ธฐ
### ๐Ÿ’ป ๊ตฌํ˜„ ์ฝ”๋“œ
#include <iostream>
#include <vector>
#include <omp.h>
#include <cuda_runtime.h>

// ํ”ฝ์…€ ๊ตฌ์กฐ์ฒด
struct Pixel {
    unsigned char r, g, b, a;
};

// CUDA ์ปค๋„: ๊ฐ€์šฐ์‹œ์•ˆ ๋ธ”๋Ÿฌ
__global__ void gaussianBlurKernel(Pixel* input, Pixel* output, 
                                   int width, int height, float sigma) {
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;
    
    if (x >= width || y >= height) return;
    
    const int radius = 5;
    float sumR = 0, sumG = 0, sumB = 0, sumWeight = 0;
    
    for (int dy = -radius; dy <= radius; dy++) {
        for (int dx = -radius; dx <= radius; dx++) {
            int nx = x + dx;
            int ny = y + dy;
            
            if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
                float distance = sqrtf(dx * dx + dy * dy);
                float weight = expf(-(distance * distance) / (2 * sigma * sigma));
                
                int idx = ny * width + nx;
                sumR += input[idx].r * weight;
                sumG += input[idx].g * weight;
                sumB += input[idx].b * weight;
                sumWeight += weight;
            }
        }
    }
    
    int idx = y * width + x;
    output[idx].r = static_cast<unsigned char>(sumR / sumWeight);
    output[idx].g = static_cast<unsigned char>(sumG / sumWeight);
    output[idx].b = static_cast<unsigned char>(sumB / sumWeight);
    output[idx].a = input[idx].a;
}

// CUDA ์ปค๋„: ์—ฃ์ง€ ๊ฒ€์ถœ (Sobel)
__global__ void sobelEdgeKernel(Pixel* input, Pixel* output, 
                                int width, int height) {
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;
    
    if (x >= width || y >= height || x == 0 || y == 0 || 
        x == width - 1 || y == height - 1) return;
    
    // Sobel ์—ฐ์‚ฐ์ž
    int gx = 0, gy = 0;
    
    for (int dy = -1; dy <= 1; dy++) {
        for (int dx = -1; dx <= 1; dx++) {
            int idx = (y + dy) * width + (x + dx);
            int gray = (input[idx].r + input[idx].g + input[idx].b) / 3;
            
            // Gx ๊ณ„์‚ฐ
            if (dx == -1) gx -= gray * (dy == 0 ? 2 : 1);
            if (dx == 1) gx += gray * (dy == 0 ? 2 : 1);
            
            // Gy ๊ณ„์‚ฐ
            if (dy == -1) gy -= gray * (dx == 0 ? 2 : 1);
            if (dy == 1) gy += gray * (dx == 0 ? 2 : 1);
        }
    }
    
    int magnitude = static_cast<int>(sqrtf(gx * gx + gy * gy));
    magnitude = min(255, magnitude);
    
    int idx = y * width + x;
    output[idx].r = output[idx].g = output[idx].b = magnitude;
    output[idx].a = 255;
}

class ImageProcessor {
private:
    std::vector<Pixel> hostImage;
    Pixel *deviceInput, *deviceOutput;
    int width, height;
    
public:
    ImageProcessor(int w, int h) : width(w), height(h) {
        hostImage.resize(w * h);
        
        size_t bytes = w * h * sizeof(Pixel);
        cudaMalloc(&deviceInput, bytes);
        cudaMalloc(&deviceOutput, bytes);
    }
    
    ~ImageProcessor() {
        cudaFree(deviceInput);
        cudaFree(deviceOutput);
    }
    
    // OpenMP๋กœ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (ํ…Œ์ŠคํŠธ์šฉ)
    void generateTestImage() {
        #pragma omp parallel for collapse(2)
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int idx = y * width + x;
                
                // ๊ทธ๋ผ๋””์–ธํŠธ ํŒจํ„ด
                hostImage[idx].r = (x * 255) / width;
                hostImage[idx].g = (y * 255) / height;
                hostImage[idx].b = ((x + y) * 255) / (width + height);
                hostImage[idx].a = 255;
            }
        }
        std::cout << "OpenMP๋กœ ํ…Œ์ŠคํŠธ ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์™„๋ฃŒ! โœ…" << std::endl;
    }
    
    // CUDA๋กœ ๊ฐ€์šฐ์‹œ์•ˆ ๋ธ”๋Ÿฌ ์ ์šฉ
    void applyGaussianBlur(float sigma) {
        size_t bytes = width * height * sizeof(Pixel);
        
        // ํ˜ธ์ŠคํŠธ โ†’ ๋””๋ฐ”์ด์Šค
        cudaMemcpy(deviceInput, hostImage.data(), bytes, cudaMemcpyHostToDevice);
        
        // ์ปค๋„ ์‹คํ–‰
        dim3 blockSize(16, 16);
        dim3 gridSize((width + 15) / 16, (height + 15) / 16);
        
        gaussianBlurKernel<<<gridSize, blockSize>>>(
            deviceInput, deviceOutput, width, height, sigma);
        
        // ๋””๋ฐ”์ด์Šค โ†’ ํ˜ธ์ŠคํŠธ
        cudaMemcpy(hostImage.data(), deviceOutput, bytes, cudaMemcpyDeviceToHost);
        
        std::cout << "CUDA๋กœ ๊ฐ€์šฐ์‹œ์•ˆ ๋ธ”๋Ÿฌ ์ ์šฉ ์™„๋ฃŒ! โœ…" << std::endl;
    }
    
    // CUDA๋กœ ์—ฃ์ง€ ๊ฒ€์ถœ
    void applyEdgeDetection() {
        size_t bytes = width * height * sizeof(Pixel);
        
        cudaMemcpy(deviceInput, hostImage.data(), bytes, cudaMemcpyHostToDevice);
        
        dim3 blockSize(16, 16);
        dim3 gridSize((width + 15) / 16, (height + 15) / 16);
        
        sobelEdgeKernel<<<gridSize, blockSize>>>(
            deviceInput, deviceOutput, width, height);
        
        cudaMemcpy(hostImage.data(), deviceOutput, bytes, cudaMemcpyDeviceToHost);
        
        std::cout << "CUDA๋กœ ์—ฃ์ง€ ๊ฒ€์ถœ ์™„๋ฃŒ! โœ…" << std::endl;
    }
    
    // OpenMP๋กœ ๋ฐ๊ธฐ ์กฐ์ •
    void adjustBrightness(float factor) {
        #pragma omp parallel for
        for (int i = 0; i < width * height; i++) {
            hostImage[i].r = min(255, static_cast<int>(hostImage[i].r * factor));
            hostImage[i].g = min(255, static_cast<int>(hostImage[i].g * factor));
            hostImage[i].b = min(255, static_cast<int>(hostImage[i].b * factor));
        }
        std::cout << "OpenMP๋กœ ๋ฐ๊ธฐ ์กฐ์ • ์™„๋ฃŒ! โœ…" << std::endl;
    }
    
    // OpenMP๋กœ ํ†ต๊ณ„ ๊ณ„์‚ฐ
    void calculateStatistics() {
        long long sumR = 0, sumG = 0, sumB = 0;
        
        #pragma omp parallel for reduction(+:sumR,sumG,sumB)
        for (int i = 0; i < width * height; i++) {
            sumR += hostImage[i].r;
            sumG += hostImage[i].g;
            sumB += hostImage[i].b;
        }
        
        int totalPixels = width * height;
        std::cout << "ํ‰๊ท  ์ƒ‰์ƒ - R: " << (sumR / totalPixels) 
                  << ", G: " << (sumG / totalPixels)
                  << ", B: " << (sumB / totalPixels) << std::endl;
    }
};

int main() {
    std::cout << "๐ŸŽจ ๊ณ ์„ฑ๋Šฅ ์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ ์‹œ์ž‘!" << std::endl;
    std::cout << "========================================" << std::endl;
    
    // 4K ํ•ด์ƒ๋„ ์ด๋ฏธ์ง€
    const int WIDTH = 3840;
    const int HEIGHT = 2160;
    
    ImageProcessor processor(WIDTH, HEIGHT);
    
    // 1๋‹จ๊ณ„: OpenMP๋กœ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
    std::cout << "\n[1๋‹จ๊ณ„] ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ค‘..." << std::endl;
    processor.generateTestImage();
    
    // 2๋‹จ๊ณ„: OpenMP๋กœ ์ „์ฒ˜๋ฆฌ
    std::cout << "\n[2๋‹จ๊ณ„] ์ „์ฒ˜๋ฆฌ ์ค‘..." << std::endl;
    processor.adjustBrightness(1.2f);
    
    // 3๋‹จ๊ณ„: CUDA๋กœ ํ•„ํ„ฐ ์ ์šฉ
    std::cout << "\n[3๋‹จ๊ณ„] GPU ํ•„ํ„ฐ ์ ์šฉ ์ค‘..." << std::endl;
    processor.applyGaussianBlur(2.0f);
    
    // 4๋‹จ๊ณ„: CUDA๋กœ ์—ฃ์ง€ ๊ฒ€์ถœ
    std::cout << "\n[4๋‹จ๊ณ„] ์—ฃ์ง€ ๊ฒ€์ถœ ์ค‘..." << std::endl;
    processor.applyEdgeDetection();
    
    // 5๋‹จ๊ณ„: OpenMP๋กœ ํ†ต๊ณ„ ๊ณ„์‚ฐ
    std::cout << "\n[5๋‹จ๊ณ„] ํ†ต๊ณ„ ๊ณ„์‚ฐ ์ค‘..." << std::endl;
    processor.calculateStatistics();
    
    std::cout << "\n========================================" << std::endl;
    std::cout << "โœจ ๋ชจ๋“  ์ฒ˜๋ฆฌ ์™„๋ฃŒ! 4K ์ด๋ฏธ์ง€๋ฅผ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฒ˜๋ฆฌํ–ˆ์–ด!" << std::endl;
    
    return 0;
}
๐ŸŽฏ ์‹ค์ „ ํŒ

์ด ํ”„๋กœ์ ํŠธ๋Š” CPU์™€ GPU์˜ ์žฅ์ ์„ ๋ชจ๋‘ ํ™œ์šฉํ•ด. ํŒŒ์ผ I/O๋‚˜ ๊ฐ„๋‹จํ•œ ์—ฐ์‚ฐ์€ OpenMP๋กœ, ๋ณต์žกํ•œ ํ•„ํ„ฐ๋ง์€ CUDA๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฑฐ์ง€. ์‹ค์ œ ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋„ ์ด๋Ÿฐ ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ ‘๊ทผ๋ฒ•์ด ๊ฐ€์žฅ ํšจ์œจ์ ์ด์•ผ! ๐Ÿ’ช
## ๐ŸŽ“ ์„ฑ๋Šฅ ์ตœ์ ํ™” ๊ณ ๊ธ‰ ๊ธฐ๋ฒ• ์ด์ œ ์ง„์งœ ๊ณ ์ˆ˜๊ฐ€ ๋˜๊ธฐ ์œ„ํ•œ ์ตœ์ ํ™” ๊ธฐ๋ฒ•๋“ค์„ ์•Œ์•„๋ณด์ž! ๐Ÿš€

### โšก OpenMP ์ตœ์ ํ™”
1. ์Šค์ผ€์ค„๋ง ์ „๋žต

OpenMP๋Š” ์ž‘์—…์„ ์Šค๋ ˆ๋“œ์— ๋ถ„๋ฐฐํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ด.
// static: ์ž‘์—…์„ ๊ท ๋“ฑํ•˜๊ฒŒ ๋ฏธ๋ฆฌ ๋ถ„๋ฐฐ (๋น ๋ฅด์ง€๋งŒ ๋ถˆ๊ท ํ˜• ๊ฐ€๋Šฅ)
#pragma omp parallel for schedule(static)
for (int i = 0; i < N; i++) {
    // ์ž‘์—… ์‹œ๊ฐ„์ด ๋น„์Šทํ•  ๋•Œ ์ข‹์Œ
}

// dynamic: ์ž‘์—…์„ ๋™์ ์œผ๋กœ ๋ถ„๋ฐฐ (๊ท ํ˜• ์ข‹์ง€๋งŒ ์˜ค๋ฒ„ํ—ค๋“œ ์žˆ์Œ)
#pragma omp parallel for schedule(dynamic, 10)
for (int i = 0; i < N; i++) {
    // ์ž‘์—… ์‹œ๊ฐ„์ด ๋‹ค๋ฅผ ๋•Œ ์ข‹์Œ
}

// guided: ์ฒ˜์Œ์—” ํฐ ์ฒญํฌ, ๋‚˜์ค‘์—” ์ž‘์€ ์ฒญํฌ
#pragma omp parallel for schedule(guided)
for (int i = 0; i < N; i++) {
    // ์ž‘์—… ์‹œ๊ฐ„์ด ์ ์  ์ค„์–ด๋“ค ๋•Œ ์ข‹์Œ
}

// auto: ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์•Œ์•„์„œ ์„ ํƒ
#pragma omp parallel for schedule(auto)
for (int i = 0; i < N; i++) {
    // ์ž˜ ๋ชจ๋ฅด๊ฒ ์œผ๋ฉด ์ด๊ฑฐ!
}
2. False Sharing ๋ฐฉ์ง€

์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๊ฐ™์€ ์บ์‹œ ๋ผ์ธ์„ ๊ณต์œ ํ•˜๋ฉด ์„ฑ๋Šฅ์ด ๋–จ์–ด์ ธ. ์ด๊ฑธ ๋ฐฉ์ง€ํ•˜๋Š” ๋ฐฉ๋ฒ•!
// ๋‚˜์œ ์˜ˆ: False Sharing ๋ฐœ์ƒ
struct BadCounter {
    int count[8]; // ๋ชจ๋‘ ๊ฐ™์€ ์บ์‹œ ๋ผ์ธ์— ์žˆ์„ ์ˆ˜ ์žˆ์Œ
};

BadCounter counter;
#pragma omp parallel for
for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 1000000; j++) {
        counter.count[i]++; // ์บ์‹œ ๋ผ์ธ ๊ฒฝ์Ÿ!
    }
}

// ์ข‹์€ ์˜ˆ: ํŒจ๋”ฉ์œผ๋กœ ์บ์‹œ ๋ผ์ธ ๋ถ„๋ฆฌ
struct GoodCounter {
    alignas(64) int count; // 64๋ฐ”์ดํŠธ ์ •๋ ฌ (์บ์‹œ ๋ผ์ธ ํฌ๊ธฐ)
};

GoodCounter counters[8];
#pragma omp parallel for
for (int i = 0; i < 8; i++) {
    for (int j = 0; j < 1000000; j++) {
        counters[i].count++; // ๊ฐ์ž ๋‹ค๋ฅธ ์บ์‹œ ๋ผ์ธ!
    }
}
### ๐Ÿ”ฅ CUDA ์ตœ์ ํ™”
1. ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ ํŒจํ„ด ์ตœ์ ํ™”

์—ฐ์†์ ์ธ ๋ฉ”๋ชจ๋ฆฌ ์ ‘๊ทผ์ด ํ•ต์‹ฌ์ด์•ผ! (Coalesced Access)
// ๋‚˜์œ ์˜ˆ: ๋น„์—ฐ์†์  ์ ‘๊ทผ
__global__ void badMemoryAccess(float* data, int width, int height) {
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;
    
    // ์—ด ์šฐ์„  ์ ‘๊ทผ - ๋А๋ฆผ!
    int idx = x * height + y;
    data[idx] = data[idx] * 2.0f;
}

// ์ข‹์€ ์˜ˆ: ์—ฐ์†์  ์ ‘๊ทผ
__global__ void goodMemoryAccess(float* data, int width, int height) {
    int x = blockIdx.x * blockDim.x + threadIdx.x;
    int y = blockIdx.y * blockDim.y + threadIdx.y;
    
    // ํ–‰ ์šฐ์„  ์ ‘๊ทผ - ๋น ๋ฆ„!
    int idx = y * width + x;
    data[idx] = data[idx] * 2.0f;
}
2. ์ŠคํŠธ๋ฆผ์„ ํ™œ์šฉํ•œ ๋น„๋™๊ธฐ ์‹คํ–‰

์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋™์‹œ์— ์‹คํ–‰ํ•ด์„œ GPU ํ™œ์šฉ๋„๋ฅผ ๋†’์ด์ž!
#include <cuda_runtime.h>

void processWithStreams(float* h_data, int totalSize) {
    const int NUM_STREAMS = 4;
    const int chunkSize = totalSize / NUM_STREAMS;
    
    cudaStream_t streams[NUM_STREAMS];
    float *d_data[NUM_STREAMS];
    
    // ์ŠคํŠธ๋ฆผ ์ƒ์„ฑ
    for (int i = 0; i < NUM_STREAMS; i++) {
        cudaStreamCreate(&streams[i]);
        cudaMalloc(&d_data[i], chunkSize * sizeof(float));
    }
    
    // ๋น„๋™๊ธฐ ์‹คํ–‰ - ๋ชจ๋“  ์ŠคํŠธ๋ฆผ์ด ๋™์‹œ์— ์ž‘๋™!
    for (int i = 0; i < NUM_STREAMS; i++) {
        int offset = i * chunkSize;
        
        // ๋น„๋™๊ธฐ ๋ฉ”๋ชจ๋ฆฌ ๋ณต์‚ฌ
        cudaMemcpyAsync(d_data[i], h_data + offset, 
                       chunkSize * sizeof(float),
                       cudaMemcpyHostToDevice, streams[i]);
        
        // ๋น„๋™๊ธฐ ์ปค๋„ ์‹คํ–‰
        int threadsPerBlock = 256;
        int blocksPerGrid = (chunkSize + threadsPerBlock - 1) / threadsPerBlock;
        processKernel<<<blocksPerGrid, threadsPerBlock, 0, streams[i]>>>(
            d_data[i], chunkSize);
        
        // ๋น„๋™๊ธฐ ๋ฉ”๋ชจ๋ฆฌ ๋ณต์‚ฌ
        cudaMemcpyAsync(h_data + offset, d_data[i],
                       chunkSize * sizeof(float),
                       cudaMemcpyDeviceToHost, streams[i]);
    }
    
    // ๋ชจ๋“  ์ŠคํŠธ๋ฆผ ์™„๋ฃŒ ๋Œ€๊ธฐ
    for (int i = 0; i < NUM_STREAMS; i++) {
        cudaStreamSynchronize(streams[i]);
        cudaStreamDestroy(streams[i]);
        cudaFree(d_data[i]);
    }
}
โš ๏ธ ์„ฑ๋Šฅ ์ธก์ •์˜ ์ค‘์š”์„ฑ

์ตœ์ ํ™”๋Š” ๋ฐ˜๋“œ์‹œ ์ธก์ •๊ณผ ํ•จ๊ป˜ ํ•ด์•ผ ํ•ด! ์ถ”์ธก๋งŒ์œผ๋กœ๋Š” ์•ˆ ๋ผ. CUDA์—๋Š” nvprof๋‚˜ Nsight ๊ฐ™์€ ํ›Œ๋ฅญํ•œ ํ”„๋กœํŒŒ์ผ๋ง ๋„๊ตฌ๋“ค์ด ์žˆ์–ด. ์ด๊ฑธ ์‚ฌ์šฉํ•ด์„œ ๋ณ‘๋ชฉ ์ง€์ ์„ ์ฐพ๊ณ , ์ตœ์ ํ™”ํ•˜๊ณ , ๋‹ค์‹œ ์ธก์ •ํ•˜๋Š” ๊ณผ์ •์„ ๋ฐ˜๋ณตํ•ด์•ผ ํ•ด! ๐Ÿ“Š
### ๐ŸŽฏ ์‹ค์ „ ๋ฒค์น˜๋งˆํ‚น
#include <iostream>
#include <chrono>
#include <cuda_runtime.h>

class PerformanceTimer {
private:
    std::chrono::high_resolution_clock::time_point start;
    std::string name;
    
public:
    PerformanceTimer(const std::string& n) : name(n) {
        start = std::chrono::high_resolution_clock::now();
    }
    
    ~PerformanceTimer() {
        auto end = std::chrono::high_resolution_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
        std::cout << name << ": " << duration.count() / 1000.0 << "ms" << std::endl;
    }
};

class CUDATimer {
private:
    cudaEvent_t start, stop;
    std::string name;
    
public:
    CUDATimer(const std::string& n) : name(n) {
        cudaEventCreate(&start);
        cudaEventCreate(&stop);
        cudaEventRecord(start);
    }
    
    ~CUDATimer() {
        cudaEventRecord(stop);
        cudaEventSynchronize(stop);
        
        float milliseconds = 0;
        cudaEventElapsedTime(&milliseconds, start, stop);
        std::cout << name << ": " << milliseconds << "ms" << std::endl;
        
        cudaEventDestroy(start);
        cudaEventDestroy(stop);
    }
};

// ์‚ฌ์šฉ ์˜ˆ์‹œ
void benchmarkExample() {
    const int N = 10000000;
    float *data = new float[N];
    
    {
        PerformanceTimer timer("CPU ์ดˆ๊ธฐํ™”");
        #pragma omp parallel for
        for (int i = 0; i < N; i++) {
            data[i] = static_cast<float>(i);
        }
    }
    
    float *d_data;
    cudaMalloc(&d_data, N * sizeof(float));
    
    {
        CUDATimer timer("GPU ๋ฉ”๋ชจ๋ฆฌ ๋ณต์‚ฌ");
        cudaMemcpy(d_data, data, N * sizeof(float), cudaMemcpyHostToDevice);
    }
    
    {
        CUDATimer timer("GPU ์ปค๋„ ์‹คํ–‰");
        int threadsPerBlock = 256;
        int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
        processKernel<<<blocksPerGrid, threadsPerBlock>>>(d_data, N);
    }
    
    delete[] data;
    cudaFree(d_data);
}
## ๐ŸŒŸ ์‹ค๋ฌด์—์„œ์˜ ํ™œ์šฉ ์‚ฌ๋ก€ ์ด๋ก ๋งŒ ๋ฐฐ์šฐ๋ฉด ์žฌ๋ฏธ์—†์ž–์•„? ์‹ค์ œ๋กœ ์–ด๋””์— ์“ฐ์ด๋Š”์ง€ ์•Œ์•„๋ณด์ž! ๐ŸŽช

### ๐ŸŽฎ ๊ฒŒ์ž„ ๊ฐœ๋ฐœ
๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜

์ตœ์‹  ๊ฒŒ์ž„๋“ค์€ ์ˆ˜์ฒœ ๊ฐœ์˜ ๋ฌผ์ฒด๊ฐ€ ์ƒํ˜ธ์ž‘์šฉํ•ด. ์ด๊ฑธ ์‹ค์‹œ๊ฐ„์œผ๋กœ ๊ณ„์‚ฐํ•˜๋ ค๋ฉด ๋ณ‘๋ ฌ ์ปดํ“จํŒ…์ด ํ•„์ˆ˜์•ผ!
// ๊ฐ„๋‹จํ•œ ํŒŒํ‹ฐํด ์‹œ์Šคํ…œ
struct Particle {
    float x, y, z;      // ์œ„์น˜
    float vx, vy, vz;   // ์†๋„
    float mass;         // ์งˆ๋Ÿ‰
};

__global__ void updateParticles(Particle* particles, int count, float dt) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx >= count) return;
    
    Particle& p = particles[idx];
    
    // ์ค‘๋ ฅ ์ ์šฉ
    p.vy -= 9.8f * dt;
    
    // ์œ„์น˜ ์—…๋ฐ์ดํŠธ
    p.x += p.vx * dt;
    p.y += p.vy * dt;
    p.z += p.vz * dt;
    
    // ๋ฐ”๋‹ฅ ์ถฉ๋Œ
    if (p.y < 0.0f) {
        p.y = 0.0f;
        p.vy = -p.vy * 0.8f; // ๋ฐ˜๋ฐœ ๊ณ„์ˆ˜
    }
}

// ์ˆ˜๋งŒ ๊ฐœ์˜ ํŒŒํ‹ฐํด์„ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ฒ˜๋ฆฌ!
void simulateParticles(Particle* d_particles, int count) {
    int threadsPerBlock = 256;
    int blocksPerGrid = (count + threadsPerBlock - 1) / threadsPerBlock;
    
    updateParticles<<<blocksPerGrid, threadsPerBlock>>>(
        d_particles, count, 0.016f); // 60 FPS
}
### ๐Ÿงฌ ๊ณผํ•™ ์—ฐ๊ตฌ
๋ถ„์ž ๋™์—ญํ•™ ์‹œ๋ฎฌ๋ ˆ์ด์…˜

์‹ ์•ฝ ๊ฐœ๋ฐœ์ด๋‚˜ ์žฌ๋ฃŒ ๊ณผํ•™์—์„œ ์ˆ˜๋ฐฑ๋งŒ ๊ฐœ์˜ ์›์ž ์ƒํ˜ธ์ž‘์šฉ์„ ๊ณ„์‚ฐํ•ด์•ผ ํ•ด.
__global__ void calculateForces(float3* positions, float3* forces, 
                               int numAtoms) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i >= numAtoms) return;
    
    float3 force = make_float3(0.0f, 0.0f, 0.0f);
    float3 pos_i = positions[i];
    
    // ๋ชจ๋“  ๋‹ค๋ฅธ ์›์ž์™€์˜ ์ƒํ˜ธ์ž‘์šฉ ๊ณ„์‚ฐ
    for (int j = 0; j < numAtoms; j++) {
        if (i == j) continue;
        
        float3 pos_j = positions[j];
        float3 r = make_float3(
            pos_j.x - pos_i.x,
            pos_j.y - pos_i.y,
            pos_j.z - pos_i.z
        );
        
        float dist = sqrtf(r.x*r.x + r.y*r.y + r.z*r.z);
        
        if (dist < 10.0f && dist > 0.1f) {
            // Lennard-Jones ํฌํ…์…œ
            float sigma = 1.0f;
            float epsilon = 1.0f;
            float s6 = powf(sigma / dist, 6);
            float s12 = s6 * s6;
            
            float f_magnitude = 24.0f * epsilon * (2.0f * s12 - s6) / dist;
            
            force.x += f_magnitude * r.x / dist;
            force.y += f_magnitude * r.y / dist;
            force.z += f_magnitude * r.z / dist;
        }
    }
    
    forces[i] = force;
}
### ๐Ÿค– ์ธ๊ณต์ง€๋Šฅ / ๋”ฅ๋Ÿฌ๋‹
ํ–‰๋ ฌ ์—ฐ์‚ฐ ๊ฐ€์†

๋”ฅ๋Ÿฌ๋‹์˜ ํ•ต์‹ฌ์€ ๊ฑฐ๋Œ€ํ•œ ํ–‰๋ ฌ ๊ณฑ์…ˆ์ด์•ผ. CUDA๊ฐ€ ์—†์—ˆ๋‹ค๋ฉด ํ˜„๋Œ€ AI๋Š” ๋ถˆ๊ฐ€๋Šฅํ–ˆ์„ ๊ฑฐ์•ผ!
์‹ค์ œ๋กœ PyTorch๋‚˜ TensorFlow ๊ฐ™์€ ํ”„๋ ˆ์ž„์›Œํฌ๋“ค์€ ๋‚ด๋ถ€์ ์œผ๋กœ CUDA๋ฅผ ์‚ฌ์šฉํ•ด. ๊ทธ๋ž˜์„œ GPU๊ฐ€ ์žˆ์œผ๋ฉด ํ•™์Šต ์†๋„๊ฐ€ 10๋ฐฐ~100๋ฐฐ ๋นจ๋ผ์ง€๋Š” ๊ฑฐ์ง€! ๐Ÿš€

### ๐Ÿ’ฐ ๊ธˆ์œต ๊ณตํ•™
๋ชฌํ…Œ์นด๋ฅผ๋กœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜

์˜ต์…˜ ๊ฐ€๊ฒฉ ๊ณ„์‚ฐ์ด๋‚˜ ๋ฆฌ์Šคํฌ ๋ถ„์„์—์„œ ์ˆ˜๋ฐฑ๋งŒ ๋ฒˆ์˜ ์‹œ๋ฎฌ๋ ˆ์ด์…˜์„ ๋Œ๋ ค์•ผ ํ•ด.
__global__ void monteCarloOption(float* results, float S0, float K, 
                                 float r, float sigma, float T, 
                                 int numPaths, unsigned int seed) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx >= numPaths) return;
    
    // ๊ฐ ์Šค๋ ˆ๋“œ๋งˆ๋‹ค ๋‹ค๋ฅธ ๋‚œ์ˆ˜ ์‹œ๋“œ
    curandState state;
    curand_init(seed, idx, 0, &state);
    
    // ์ฃผ๊ฐ€ ๊ฒฝ๋กœ ์‹œ๋ฎฌ๋ ˆ์ด์…˜
    float S = S0;
    int steps = 252; // 1๋…„ = 252 ๊ฑฐ๋ž˜์ผ
    float dt = T / steps;
    
    for (int i = 0; i < steps; i++) {
        float z = curand_normal(&state);
        S *= expf((r - 0.5f * sigma * sigma) * dt + sigma * sqrtf(dt) * z);
    }
    
    // ์ฝœ ์˜ต์…˜ ํŽ˜์ด์˜คํ”„
    results[idx] = fmaxf(S - K, 0.0f) * expf(-r * T);
}

// ์ˆ˜๋ฐฑ๋งŒ ๊ฐœ์˜ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๋ช‡ ์ดˆ ๋งŒ์— ๊ณ„์‚ฐ!
๐Ÿ’ผ ์žฌ๋Šฅ๋„ท์—์„œ์˜ ๊ธฐํšŒ

์ด๋Ÿฐ ๊ณ ๊ธ‰ ๊ธฐ์ˆ ์„ ๊ฐ€์ง„ ๊ฐœ๋ฐœ์ž๋Š” ์ •๋ง ๊ท€ํ•ด! **์žฌ๋Šฅ๋„ท** ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ๋ณ‘๋ ฌ ์ปดํ“จํŒ… ์ „๋ฌธ๊ฐ€๋กœ ํ™œ๋™ํ•˜๋ฉด, ๊ฒŒ์ž„ ๊ฐœ๋ฐœ, ๊ณผํ•™ ์—ฐ๊ตฌ, ๊ธˆ์œต ๋ถ„์„ ๋“ฑ ๋‹ค์–‘ํ•œ ๋ถ„์•ผ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด. ํŠนํžˆ CUDA ์ตœ์ ํ™” ๊ฐ™์€ ์ „๋ฌธ ๊ธฐ์ˆ ์€ ๋†’์€ ๊ฐ€์น˜๋ฅผ ์ธ์ •๋ฐ›์„ ์ˆ˜ ์žˆ์ง€! ๐Ÿ’Ž
## ๐ŸŽ“ ํ•™์Šต ๋กœ๋“œ๋งต๊ณผ ์ถ”์ฒœ ์ž๋ฃŒ ์ž, ์ด์ œ ์–ด๋–ป๊ฒŒ ๊ณต๋ถ€ํ•ด์•ผ ํ• ์ง€ ์•Œ๋ ค์ค„๊ฒŒ! ๐Ÿ“š

### ๐Ÿ—บ๏ธ ๋‹จ๊ณ„๋ณ„ ํ•™์Šต ๊ฒฝ๋กœ
์ดˆ๊ธ‰ (1-2๊ฐœ์›”)

โœ… C++ ๊ธฐ์ดˆ ๋ฌธ๋ฒ• ์™„๋ฒฝํžˆ ์ตํžˆ๊ธฐ
โœ… ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ ๊ฐœ๋… ์ดํ•ด
โœ… OpenMP ๊ธฐ๋ณธ ์ง€์‹œ๋ฌธ ์—ฐ์Šต
โœ… ๊ฐ„๋‹จํ•œ ๋ณ‘๋ ฌ ํ”„๋กœ๊ทธ๋žจ ์ž‘์„ฑ

์ค‘๊ธ‰ (3-4๊ฐœ์›”)

โœ… OpenMP ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ (์Šค์ผ€์ค„๋ง, reduction ๋“ฑ)
โœ… CUDA ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ชจ๋ธ ์ดํ•ด
โœ… ๊ธฐ๋ณธ CUDA ์ปค๋„ ์ž‘์„ฑ
โœ… ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ ์ตํžˆ๊ธฐ
โœ… ์„ฑ๋Šฅ ์ธก์ • ๋ฐ ํ”„๋กœํŒŒ์ผ๋ง

๊ณ ๊ธ‰ (6๊ฐœ์›” ์ด์ƒ)

โœ… CUDA ๋ฉ”๋ชจ๋ฆฌ ์ตœ์ ํ™”
โœ… Shared Memory, Constant Memory ํ™œ์šฉ
โœ… ์ŠคํŠธ๋ฆผ๊ณผ ๋น„๋™๊ธฐ ์‹คํ–‰
โœ… ๋‹ค์ค‘ GPU ํ”„๋กœ๊ทธ๋ž˜๋ฐ
โœ… ์‹ค์ „ ํ”„๋กœ์ ํŠธ ๊ฒฝํ—˜
### ๐Ÿ“– ์ถ”์ฒœ ํ•™์Šต ์ž๋ฃŒ
๋ถ„์•ผ ์ž๋ฃŒ ๋‚œ์ด๋„
OpenMP OpenMP ๊ณต์‹ ๋ฌธ์„œ โญโญ
OpenMP "Using OpenMP" by Chapman et al. โญโญโญ
CUDA NVIDIA CUDA C Programming Guide โญโญโญ
CUDA "Programming Massively Parallel Processors" โญโญโญโญ
๋ณ‘๋ ฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜ "Parallel Programming in C with MPI and OpenMP" โญโญโญโญ
์‹ค์ „ NVIDIA Developer Blog โญโญโญ
### ๐Ÿ’ป ์‹ค์Šต ํ”„๋กœ์ ํŠธ ์•„์ด๋””์–ด

๐ŸŽจ ์ดˆ๊ธ‰ ํ”„๋กœ์ ํŠธ

โ€ข ์ด๋ฏธ์ง€ ํ•„ํ„ฐ (๊ทธ๋ ˆ์ด์Šค์ผ€์ผ, ๋ธ”๋Ÿฌ)
โ€ข ๋ฐฐ์—ด ์ •๋ ฌ ์•Œ๊ณ ๋ฆฌ์ฆ˜
โ€ข ๊ฐ„๋‹จํ•œ ์ˆ˜ํ•™ ์—ฐ์‚ฐ (ํ–‰๋ ฌ ๋ง์…ˆ)
โ€ข ํŒŒ์ผ ๋ฐ์ดํ„ฐ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ

๐Ÿš€ ์ค‘๊ธ‰ ํ”„๋กœ์ ํŠธ

โ€ข ์ด๋ฏธ์ง€ ์ปจ๋ณผ๋ฃจ์…˜
โ€ข ํ–‰๋ ฌ ๊ณฑ์…ˆ ์ตœ์ ํ™”
โ€ข ํŒŒํ‹ฐํด ์‹œ์Šคํ…œ
โ€ข ๋ ˆ์ด ํŠธ๋ ˆ์ด์‹ฑ ๊ธฐ์ดˆ

๐Ÿ”ฅ ๊ณ ๊ธ‰ ํ”„๋กœ์ ํŠธ

โ€ข ๋”ฅ๋Ÿฌ๋‹ ๋ ˆ์ด์–ด ๊ตฌํ˜„
โ€ข ๋ฌผ๋ฆฌ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ์—”์ง„
โ€ข ์‹ค์‹œ๊ฐ„ ๋น„๋””์˜ค ์ฒ˜๋ฆฌ
โ€ข ๋ถ„์ž ๋™์—ญํ•™ ์‹œ๋ฎฌ๋ ˆ์ด์…˜

๐Ÿ’Ž ์ „๋ฌธ๊ฐ€ ํ”„๋กœ์ ํŠธ

โ€ข ์ปค์Šคํ…€ ๋”ฅ๋Ÿฌ๋‹ ํ”„๋ ˆ์ž„์›Œํฌ
โ€ข ๊ฒŒ์ž„ ์—”์ง„ ๋ฌผ๋ฆฌ ์‹œ์Šคํ…œ
โ€ข ๊ธˆ์œต ๋ฆฌ์Šคํฌ ๋ถ„์„ ๋„๊ตฌ
โ€ข ๊ณผํ•™ ์‹œ๋ฎฌ๋ ˆ์ด์…˜ ํ”Œ๋žซํผ
## ๐Ÿ› ์ž์ฃผ ํ•˜๋Š” ์‹ค์ˆ˜์™€ ํ•ด๊ฒฐ๋ฒ• ์‹ค์ „์—์„œ ์ž์ฃผ ๋งˆ์ฃผ์น˜๋Š” ๋ฌธ์ œ๋“ค์„ ์ •๋ฆฌํ•ด๋ดค์–ด! ๐Ÿ”ง

### โŒ OpenMP ์‹ค์ˆ˜๋“ค
1. ๋ ˆ์ด์Šค ์ปจ๋””์…˜ (Race Condition)

์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ ๋ณ€์ˆ˜๋ฅผ ์ˆ˜์ •ํ•˜๋ ค๊ณ  ํ•  ๋•Œ ๋ฐœ์ƒํ•ด.
// ์ž˜๋ชป๋œ ์ฝ”๋“œ
int sum = 0;
#pragma omp parallel for
for (int i = 0; i < 1000; i++) {
    sum += i; // ๋ ˆ์ด์Šค ์ปจ๋””์…˜! ๊ฒฐ๊ณผ๊ฐ€ ๋งค๋ฒˆ ๋‹ฌ๋ผ์ ธ
}

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ 1: reduction ์‚ฌ์šฉ
int sum = 0;
#pragma omp parallel for reduction(+:sum)
for (int i = 0; i < 1000; i++) {
    sum += i; // ์•ˆ์ „!
}

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ 2: critical ์‚ฌ์šฉ (๋А๋ฆผ)
int sum = 0;
#pragma omp parallel for
for (int i = 0; i < 1000; i++) {
    #pragma omp critical
    {
        sum += i; // ์•ˆ์ „ํ•˜์ง€๋งŒ ๋А๋ฆผ
    }
}

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ 3: atomic ์‚ฌ์šฉ (๋น ๋ฆ„)
int sum = 0;
#pragma omp parallel for
for (int i = 0; i < 1000; i++) {
    #pragma omp atomic
    sum += i; // ์•ˆ์ „ํ•˜๊ณ  ๋น ๋ฆ„
}
2. ๋ฐ์ดํ„ฐ ์˜์กด์„ฑ ๋ฌด์‹œ

๋ฐ˜๋ณต๋ฌธ์˜ ๊ฐ ๋ฐ˜๋ณต์ด ๋…๋ฆฝ์ ์ด์ง€ ์•Š์œผ๋ฉด ๋ณ‘๋ ฌํ™”ํ•˜๋ฉด ์•ˆ ๋ผ!
// ์ž˜๋ชป๋œ ์ฝ”๋“œ - ๋ฐ์ดํ„ฐ ์˜์กด์„ฑ ์žˆ์Œ
#pragma omp parallel for
for (int i = 1; i < N; i++) {
    array[i] = array[i-1] + 1; // i๋ฒˆ์งธ๊ฐ€ i-1๋ฒˆ์งธ์— ์˜์กด!
}

// ์ด๋Ÿฐ ๊ฒฝ์šฐ๋Š” ๋ณ‘๋ ฌํ™”ํ•  ์ˆ˜ ์—†์–ด. ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰ํ•ด์•ผ ํ•ด.
### โŒ CUDA ์‹ค์ˆ˜๋“ค
1. ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜

GPU ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ํ• ๋‹นํ•˜๊ณ  ํ•ด์ œํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ ๋ถ€์กฑํ•ด์ ธ!
// ์ž˜๋ชป๋œ ์ฝ”๋“œ
void badFunction() {
    float *d_data;
    cudaMalloc(&d_data, 1000 * sizeof(float));
    // ... ์ž‘์—… ...
    // cudaFree๋ฅผ ์•ˆ ํ•จ! ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜!
}

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ - RAII ํŒจํ„ด ์‚ฌ์šฉ
class CUDAMemory {
private:
    void* ptr;
    size_t size;
    
public:
    CUDAMemory(size_t s) : size(s) {
        cudaMalloc(&ptr, size);
    }
    
    ~CUDAMemory() {
        cudaFree(ptr); // ์ž๋™์œผ๋กœ ํ•ด์ œ!
    }
    
    void* get() { return ptr; }
};

void goodFunction() {
    CUDAMemory mem(1000 * sizeof(float));
    // ... ์ž‘์—… ...
    // ์ž๋™์œผ๋กœ ํ•ด์ œ๋จ!
}
2. ์ปค๋„ ์‹คํ–‰ ์˜ค๋ฅ˜ ๋ฌด์‹œ

CUDA ์ปค๋„์€ ๋น„๋™๊ธฐ๋กœ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ค๋ฅ˜๋ฅผ ํ™•์ธํ•˜์ง€ ์•Š์œผ๋ฉด ๋””๋ฒ„๊น…์ด ์–ด๋ ค์›Œ!
// ์ž˜๋ชป๋œ ์ฝ”๋“œ
myKernel<<<blocks, threads>>>(data);
// ์˜ค๋ฅ˜ ํ™•์ธ ์•ˆ ํ•จ!

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ
myKernel<<<blocks, threads>>>(data);

// ์ปค๋„ ์‹คํ–‰ ์˜ค๋ฅ˜ ํ™•์ธ
cudaError_t err = cudaGetLastError();
if (err != cudaSuccess) {
    std::cerr << "์ปค๋„ ์‹คํ–‰ ์˜ค๋ฅ˜: " << cudaGetErrorString(err) << std::endl;
}

// ์ปค๋„ ์™„๋ฃŒ ๋Œ€๊ธฐ ๋ฐ ์˜ค๋ฅ˜ ํ™•์ธ
err = cudaDeviceSynchronize();
if (err != cudaSuccess) {
    std::cerr << "์ปค๋„ ๋™๊ธฐํ™” ์˜ค๋ฅ˜: " << cudaGetErrorString(err) << std::endl;
}
3. ์Šค๋ ˆ๋“œ ์ธ๋ฑ์Šค ๋ฒ”์œ„ ์ฒดํฌ ๋ˆ„๋ฝ

๋ฐฐ์—ด ํฌ๊ธฐ๊ฐ€ ๋ธ”๋ก ํฌ๊ธฐ์˜ ๋ฐฐ์ˆ˜๊ฐ€ ์•„๋‹ˆ๋ฉด ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚  ์ˆ˜ ์žˆ์–ด!
// ์ž˜๋ชป๋œ ์ฝ”๋“œ
__global__ void badKernel(float* data, int N) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    data[idx] = idx; // N์„ ์ดˆ๊ณผํ•  ์ˆ˜ ์žˆ์Œ!
}

// ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ
__global__ void goodKernel(float* data, int N) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < N) { // ๋ฒ”์œ„ ์ฒดํฌ!
        data[idx] = idx;
    }
}
### ๐Ÿ” ๋””๋ฒ„๊น… ํŒ
OpenMP ๋””๋ฒ„๊น…

โ€ข ํ™˜๊ฒฝ ๋ณ€์ˆ˜ OMP_NUM_THREADS=1๋กœ ์„ค์ •ํ•ด์„œ ์ˆœ์ฐจ ์‹คํ–‰์œผ๋กœ ํ…Œ์ŠคํŠธ
โ€ข -g ํ”Œ๋ž˜๊ทธ๋กœ ๋””๋ฒ„๊ทธ ์ •๋ณด ํฌํ•จ
โ€ข Valgrind๋‚˜ Thread Sanitizer ์‚ฌ์šฉ
โ€ข ์ž‘์€ ๋ฐ์ดํ„ฐ๋กœ ๋จผ์ € ํ…Œ์ŠคํŠธ

CUDA ๋””๋ฒ„๊น…

โ€ข cuda-memcheck๋กœ ๋ฉ”๋ชจ๋ฆฌ ์˜ค๋ฅ˜ ๊ฒ€์‚ฌ
โ€ข printf๋ฅผ ์ปค๋„ ์•ˆ์—์„œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ (๋””๋ฐ”์ด์Šค ์ฝ”๋“œ์—์„œ๋„!)
โ€ข cuda-gdb๋กœ ์ปค๋„ ๋””๋ฒ„๊น…
โ€ข Nsight Systems/Compute๋กœ ํ”„๋กœํŒŒ์ผ๋ง
โ€ข ์ž‘์€ ๊ทธ๋ฆฌ๋“œ/๋ธ”๋ก ํฌ๊ธฐ๋กœ ๋จผ์ € ํ…Œ์ŠคํŠธ
## ๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ ์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์™”๋‹ค๋‹ˆ ์ •๋ง ๋Œ€๋‹จํ•ด! ๐ŸŽ‰

์šฐ๋ฆฌ๊ฐ€ ํ•จ๊ป˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์ •๋ฆฌํ•ด๋ณด์ž:

โœจ ํ•ต์‹ฌ ์š”์•ฝ

1. ๋ณ‘๋ ฌ ์ปดํ“จํŒ…์˜ ๊ธฐ์ดˆ
โ€ข ๋™์‹œ์„ฑ vs ๋ณ‘๋ ฌ์„ฑ์˜ ์ฐจ์ด
โ€ข ํ”„๋กœ์„ธ์Šค์™€ ์Šค๋ ˆ๋“œ
โ€ข ๋ณ‘๋ ฌํ™”๊ฐ€ ๊ฐ€๋Šฅํ•œ ์ž‘์—…์˜ ํŠน์ง•

2. OpenMP
โ€ข CPU ๋ฉ€ํ‹ฐ์ฝ”์–ด ํ™œ์šฉ
โ€ข ๊ฐ„๋‹จํ•œ ์ง€์‹œ๋ฌธ์œผ๋กœ ๋ณ‘๋ ฌํ™”
โ€ข ์ ์ง„์  ์ตœ์ ํ™” ๊ฐ€๋Šฅ
โ€ข ๋ถˆ๊ทœ์น™ํ•œ ์ž‘์—…์— ์ ํ•ฉ

3. CUDA
โ€ข GPU์˜ ์ˆ˜์ฒœ ๊ฐœ ์ฝ”์–ด ํ™œ์šฉ
โ€ข ๋Œ€๋Ÿ‰์˜ ๊ทœ์น™์ ์ธ ์ž‘์—…์— ์ตœ์ 
โ€ข ๋ฉ”๋ชจ๋ฆฌ ๊ณ„์ธต ์ดํ•ด๊ฐ€ ์ค‘์š”
โ€ข ์ตœ๋Œ€ 100๋ฐฐ ์ด์ƒ์˜ ์†๋„ ํ–ฅ์ƒ ๊ฐ€๋Šฅ

4. ์‹ค์ „ ํ™œ์šฉ
โ€ข ๊ฒŒ์ž„, ๊ณผํ•™, ๊ธˆ์œต, AI ๋“ฑ ๋‹ค์–‘ํ•œ ๋ถ„์•ผ
โ€ข CPU์™€ GPU๋ฅผ ํ•จ๊ป˜ ํ™œ์šฉํ•˜๋Š” ํ•˜์ด๋ธŒ๋ฆฌ๋“œ ์ ‘๊ทผ
โ€ข ์„ฑ๋Šฅ ์ธก์ •๊ณผ ์ตœ์ ํ™”์˜ ์ค‘์š”์„ฑ
### ๐Ÿš€ ๋‹ค์Œ ๋‹จ๊ณ„ ๋ณ‘๋ ฌ ์ปดํ“จํŒ…์€ ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์–ด. ์•ž์œผ๋กœ ๋ฐฐ์›Œ๋ณผ ๋งŒํ•œ ๊ฒƒ๋“ค:

โ€ข **๋‹ค์ค‘ GPU ํ”„๋กœ๊ทธ๋ž˜๋ฐ**: ์—ฌ๋Ÿฌ GPU๋ฅผ ๋™์‹œ์— ํ™œ์šฉ
โ€ข **๋ถ„์‚ฐ ์ปดํ“จํŒ…**: MPI๋ฅผ ์‚ฌ์šฉํ•œ ํด๋Ÿฌ์Šคํ„ฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ
โ€ข **์ด๊ธฐ์ข… ์ปดํ“จํŒ…**: CPU, GPU, FPGA๋ฅผ ํ•จ๊ป˜ ํ™œ์šฉ
โ€ข **์ตœ์‹  ๊ธฐ์ˆ **: CUDA Graphs, Cooperative Groups ๋“ฑ
โ€ข **AI ํ”„๋ ˆ์ž„์›Œํฌ**: PyTorch, TensorFlow์˜ ๋‚ด๋ถ€ ๊ตฌ์กฐ ์ดํ•ด

### ๐Ÿ’ช ์‹ค๋ ฅ ํ–ฅ์ƒ์„ ์œ„ํ•œ ์กฐ์–ธ
1. ๊พธ์ค€ํžˆ ์—ฐ์Šตํ•˜๊ธฐ
๋งค์ผ ์กฐ๊ธˆ์”ฉ์ด๋ผ๋„ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ด. ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์  ๋ณต์žกํ•œ ๊ฒƒ์œผ๋กœ!

2. ์„ฑ๋Šฅ ์ธก์ • ์Šต๊ด€ํ™”
ํ•ญ์ƒ ์ธก์ •ํ•˜๊ณ , ๋ถ„์„ํ•˜๊ณ , ์ตœ์ ํ™”ํ•˜๋Š” ์Šต๊ด€์„ ๋“ค์—ฌ. ์ถ”์ธก์ด ์•„๋‹Œ ๋ฐ์ดํ„ฐ๋กœ ํŒ๋‹จํ•ด!

3. ์ปค๋ฎค๋‹ˆํ‹ฐ ํ™œ์šฉ
NVIDIA Developer Forums, Stack Overflow, GitHub ๋“ฑ์—์„œ ๋‹ค๋ฅธ ๊ฐœ๋ฐœ์ž๋“ค๊ณผ ์†Œํ†ตํ•ด!

4. ์‹ค์ „ ํ”„๋กœ์ ํŠธ ๊ฒฝํ—˜
์ด๋ก ๋งŒ์œผ๋กœ๋Š” ๋ถ€์กฑํ•ด. ์‹ค์ œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ฉด์„œ ๋ฐฐ์šฐ๋Š” ๊ฒŒ ๊ฐ€์žฅ ๋น ๋ฅด์ง€!

5. ์ตœ์‹  ํŠธ๋ Œ๋“œ ํŒ”๋กœ์šฐ
GPU ๊ธฐ์ˆ ์€ ๋น ๋ฅด๊ฒŒ ๋ฐœ์ „ํ•ด. NVIDIA GTC ๊ฐ™์€ ์ปจํผ๋Ÿฐ์Šค ์˜์ƒ์„ ๋ณด๋ฉด์„œ ์ตœ์‹  ๊ธฐ์ˆ ์„ ์ตํ˜€!
### ๐ŸŒˆ ๋งˆ์ง€๋ง‰ ํ•œ๋งˆ๋”” ๋ณ‘๋ ฌ ์ปดํ“จํŒ…์€ ์ฒ˜์Œ์—” ์–ด๋ ต๊ฒŒ ๋А๊ปด์งˆ ์ˆ˜ ์žˆ์–ด. ํ•˜์ง€๋งŒ ํ•œ ๋ฒˆ ์ต์ˆ™ํ•ด์ง€๋ฉด ์ •๋ง ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ๊ฐ€ ๋ผ. ์ˆœ์ฐจ ํ”„๋กœ๊ทธ๋žจ์ด ๋ช‡ ์‹œ๊ฐ„ ๊ฑธ๋ฆฌ๋˜ ์ž‘์—…์„ ๋ช‡ ์ดˆ ๋งŒ์— ๋๋‚ผ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑด ์ •๋ง ์งœ๋ฆฟํ•œ ๊ฒฝํ—˜์ด์•ผ! โšก

๊ทธ๋ฆฌ๊ณ  ์ด๋Ÿฐ ๊ธฐ์ˆ ์„ ๊ฐ€์ง„ ๊ฐœ๋ฐœ์ž๋Š” ์ •๋ง ๊ท€ํ•ด. ๊ฒŒ์ž„ ํšŒ์‚ฌ, ์—ฐ๊ตฌ์†Œ, ๊ธˆ์œต ํšŒ์‚ฌ, AI ์Šคํƒ€ํŠธ์—…... ์–ด๋””์„œ๋“  ํ™˜์˜๋ฐ›์„ ๊ฑฐ์•ผ. ์•ž์„œ ๋งํ–ˆ๋“ฏ์ด **์žฌ๋Šฅ๋„ท** ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋„ ์ด๋Ÿฐ ์ „๋ฌธ ๊ธฐ์ˆ ์€ ๋†’์€ ๊ฐ€์น˜๋ฅผ ์ธ์ •๋ฐ›์„ ์ˆ˜ ์žˆ์–ด!

์ž, ์ด์ œ ๋‹น์‹  ์ฐจ๋ก€์•ผ! ์˜ค๋Š˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ฒซ ๋ฒˆ์งธ ๋ณ‘๋ ฌ ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค์–ด๋ด. ์ž‘์€ ๊ฒƒ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์  ๋ฐœ์ „์‹œ์ผœ ๋‚˜๊ฐ€๋ฉด ๋ผ. ํ™”์ดํŒ…! ๐Ÿ’ช๐Ÿš€

๐ŸŽ“ ๋‹น์‹ ์€ ์ด์ œ ๋ณ‘๋ ฌ ์ปดํ“จํŒ…์˜ ์„ธ๊ณ„๋กœ ์ฒซ ๋ฐœ์„ ๋‚ด๋””๋Ž ์–ด!

๊ณ„์† ๋ฐฐ์šฐ๊ณ , ์‹คํ—˜ํ•˜๊ณ , ์„ฑ์žฅํ•˜์ž! ๐ŸŒŸ
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0