<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ii_seo.log</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 06 May 2024 13:17:29 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ii_seo.log</title>
            <url>https://velog.velcdn.com/images/ii_seo/profile/a0694402-6dd6-477b-9ca2-76504fc2a145/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ii_seo.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ii_seo" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[MGP] 02-1B. Thread Programming_Thread]]></title>
            <link>https://velog.io/@ii_seo/MGP-02-1B.-Thread-ProgrammingThread</link>
            <guid>https://velog.io/@ii_seo/MGP-02-1B.-Thread-ProgrammingThread</guid>
            <pubDate>Mon, 06 May 2024 13:17:29 GMT</pubDate>
            <description><![CDATA[<h1 id="❦-thread">❦ Thread</h1>
<h2 id="ෆ-create--join--detach">ෆ Create &amp; Join &amp; Detach</h2>
<pre><code>#include &lt;iostream&gt;
#include &lt;thread&gt;
#include &lt;vector&gt;
int *a, *b, *k, *c;

void mac(int tid, int num_threads)
{
    for(int i-0;i&lt;N/num_threads;i++) 
    {
        int idx = tid*(N/num_threads) + i;
        c[idx]  = k[idx] * a[idx];
        c[idx] += k[idx] * b[idx];
    }
    return;
}
int main(int argc, char* argv[])
{
...
    std::vector&lt;std::thread&gt; threads;
    for(int t=0;t&lt;NT;t++) {
        // create thread
        threads.push_back(std::thread(mac, t, NT));
    }
    for(auto&amp; thread: threads) {
        // wait for finish
        thread.join();
        // don&#39;t want to wait for finish ..
        //thread.detach();
    }
    return 0;
}</code></pre><h3 id="ಌ-create">ಌ Create</h3>
<p><code>std::thread(mac, t, NT)</code></p>
<ul>
<li>새로운 thread를 생성하는 데에 사용되는 코드<ul>
<li>새로운 thread 객체 생성</li>
<li>해당 thread에서 실행될 작업 지정 가능</li>
</ul>
</li>
<li><code>mac</code> : 호출 가능한 객체 (callable)<ul>
<li>callable : thread에서 실행될 작업을 정의하는 함수</li>
<li>별도의 thread에서 실행됨</li>
</ul>
</li>
<li><code>t</code>, <code>NT</code> : <code>mac</code>에 전달되는 인수</li>
</ul>
<h3 id="ಌ-join">ಌ Join</h3>
<p><code>thread.join();</code></p>
<ul>
<li><p>thread는 호출 가능한 객체가 반환될 때까지 기다린 뒤 반환</p>
<ul>
<li>호출 가능한 객체가 실행을 완료하면 해당 thread가 종료</li>
</ul>
</li>
<li><p>위의 코드는 호출한 thread가 대상 thread가 종료될 때까지 기다림</p>
<ul>
<li><p>이 method를 호출한 thread는 대상 thread가 종료될 때까지 블록됨</p>
<p>  → 여러 thread간에 작업의 실행 순서 조절, thread들의 실행 동기화</p>
</li>
</ul>
</li>
<li><p>thread가 이미 join되었는지 여부 확인 : <code>thread.joinable();</code></p>
<ul>
<li>부울 값을 반환
<img src="https://velog.velcdn.com/images/ii_seo/post/94c2b279-2985-46b2-9a39-8ac1cb9780cd/image.png" alt=""></li>
</ul>
</li>
<li><p><code>join();</code></p>
<ul>
<li>부모 thread가 생성한 자식 thread가 종료될 때까지 대기<ul>
<li>부모 thread는 자식 thread의 종료를 기다림</li>
<li><strong>자원을 정리</strong>하거나 결과를 처리할 수 있음</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="ಌ-detach">ಌ Detach</h3>
<p><code>thread.detach();</code></p>
<ul>
<li><p>스레드가 호출 가능한 객체가 반환될 때까지 기다리지 않고, 스레드의 실행을 기다리지 않고 다른 작업을 수행하고 싶다면, 스레드의 실행을 조인하지 않으면 됨</p>
</li>
<li><p>스레드가 호출 가능한 객체가 반환될 때까지 기다리지 않고 실행을 계속하면, 스레드의 실행이 끝나지 않았더라도 호출 가능한 객체가 반환될 때까지 기다리지 않고도 다른 작업을 수행할 수 있음</p>
</li>
<li><p>호출 가능한 객체가 반환되기 전에 해당 스레드의 리소스가 정리되지 않을 수 있음</p>
<ul>
<li>메모리 누수나 예기치 않은 동작이 발생할 수 있음
<img src="https://velog.velcdn.com/images/ii_seo/post/cca6dc31-a680-47b8-bad7-146b003ad913/image.png" alt=""></li>
</ul>
</li>
<li><p><code>detach();</code></p>
<ul>
<li>부모 thread가 생성한 자식 thread가 독립적으로 실행<ul>
<li>부모 thread는 자식 thread의 종료를 기다리지 않음</li>
<li><strong>자원을 자식 thread에게 넘기고</strong> 별도의 작업 수행 가능</li>
</ul>
</li>
</ul>
</li>
<li><p>만약 부모 thread가 자식 thread에게 할당한 자원을 정리하지 않고 <code>detach()</code> 를 호출하지 않는다면?</p>
<ul>
<li><p>자식 스레드가 종료될 때까지 부모 스레드는 해당 자원을 계속해서 유지</p>
<p>  → 메모리 누수와 같은 문제 발생</p>
</li>
</ul>
</li>
</ul>
<h3 id="ಌ-callable">ಌ Callable</h3>
<blockquote>
<p>호출 가능한 객체 : 함수처럼 호출될 수 있는 객체</p>
</blockquote>
<p><strong>1. Function pointer</strong></p>
<pre><code>void mac(..params..)
{...}
...
std::thread(mac, ..params..)</code></pre><ul>
<li>함수의 memory address를 가리키는 pointer</li>
<li>함수의 이름 또는 주소를 가리킴</li>
</ul>
<p><strong>2. Function object</strong></p>
<pre><code>class set_object
{
    public;
    void operator()(int* target)
    {
        *target = 1;
    }
};

int main()
{
    std::thread t(set_object(), &amp;a);
}</code></pre><ul>
<li>클래스 객체로서 operator() 멤버 함수를 구현함으로써 호출 가능한 객체를 만듦</li>
<li>함수처럼 호출될 수 있으며, 상태(state)를 유지할 수 있음</li>
<li>함수 pointer보다 유연하고 풍부한 기능 제공</li>
</ul>
<p><strong>3. Lambda expression</strong></p>
<pre><code>int b = 0;
std::thread t2([](int* target) {
    *target = 1;
    }, &amp;b);
t2.join();</code></pre><ul>
<li>익명 함수를 생성하는 간결한 방법</li>
<li>함수처럼 호출되며, 함수 객체를 만듦</li>
<li>주로 간단한 코드 조각 표현 시 사용</li>
</ul>
<h1 id="❦-race-condition">❦ Race Condition</h1>
<blockquote>
<p>동시에 여러 thread나 process가 shared resource에 접근할 때 발생
주로 Read-Modify-Write 연산이 동시에 수행될 때 발생</p>
</blockquote>
<p>문제는 Read-Modify-Write 연산이 여러 thread에 의해 동시에 실행될 수 있고, 이들의 실행 순서가 보장되지 않는다는 것 ,,→ 하나의 thread가 Read 연산을 수행하는 동안, 다른 thread가 그 값을 Modify하거나 Write 연산을 수행할 수 있음</p>
<pre><code>#include &lt;iostream&gt;
#include &lt;thread&gt;
#include &lt;vector&gt;

void worker(int* input, int start, int size, int* output)
{
    for(int i=0;i&lt;size;i++) {
        if(input[start+i]==0 {
            (*output)++;
        }
    }
}

int main(int argc, char* argv[])
{
    const int N = atoi(argv[1]);
    const int NT = atoi(argv[2]);
    int *array = new int[N];

    for(int i=0;i&lt;N;i++) {
        array[i] = 0;
    }

    int count=0;
    std::vector&lt;std::thread&gt; threads;
    for(int t=0;t&lt;NT;t++) {
        //assume N is a multiple of NT
        int size=N/NT;
        int start = t*size;
        threads.push_back(std::thread(worker,array,start,size,&amp;count));
    }
    for(auto&amp; thread:threads) {
        thread.join();
    }

    std::cout&lt;&lt;&quot;there are &quot;&lt;&lt;count&lt;&lt;&quot;zeros&quot;&lt;&lt;std::endl;
}</code></pre><p><img src="https://velog.velcdn.com/images/ii_seo/post/52da0f6c-6cdb-42ec-9129-20b16c4a713b/image.png" alt=""></p>
<h3 id="⇒-결과는-thread들-간의-race-condition에-따라-달라지게-됨">⇒ 결과는 thread들 간의 race condition에 따라 달라지게 됨</h3>
<h3 id="thread가-자원에-접근하는-순서와-타이밍에-따라-결과가-달라질-수-있음">(thread가 자원에 접근하는 순서와 타이밍에 따라 결과가 달라질 수 있음)</h3>
<p>race condition은 예측할 수 없는 결과를 초래하며, 디버그하기 어렵고 심각한 버그를 유발할 수 있음</p>
<p>→ 적절한 동기화 메커니즘을 사용 ~&gt; 공유 자원에 대한 접근 제어, 경쟁 조건을 회피하거나 방지</p>
<h2 id="ෆ-mutex">ෆ Mutex</h2>
<blockquote>
<p>상호 배제(mutual exclusion)를 제공하는 동기화 기법 중 하나
여러 thread간에 공유된 resource에 대한 안전한 접근을 보장</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/ii_seo/post/6d5fdca4-c0b6-4990-8ca7-6699f2fd4099/image.png" alt=""></p>
<ul>
<li><p>critical section (임계 영역)</p>
<p>  : 상호 배제(mutaual exclusion)를 필요로 하는 코드 영역</p>
<p>  → 여러 thread가 동시에 접근하면 안 되는 공유 자원에 대한 접근을 제한하는 코드 부분</p>
<ul>
<li><p>공유된 자원을 업데이트하거나 수정하는 코드 영역</p>
</li>
<li><p>여러 thread가 동시에 critical section에 접근하면, 경쟁상태가 발생할 수 있어 데이터의 일관성이 깨질 수 있음</p>
<p>  → 한 번에 단 하나의 thread만이 critical section에 접근할 수 있도록 상호 배제 매커니즘 사용</p>
</li>
</ul>
</li>
<li><p>locked 상태</p>
<p>  mutex가 잠겨 있는 상태, 이 때 mutex를 소유한 thread만이 공유 자원에 접근 가능</p>
<ul>
<li>오직 하나의 thread만이 lock을 획득할 수 있도록 보장</li>
<li>다른 thread가 mutex를 lock하려고 시도하면, 해당 thread는 mutex가 unlock될 때까지 대기해야 함</li>
<li>→ 여러 thread간에 공유된 자원에 대한 안전한 접근이 보장됨</li>
</ul>
</li>
<li><p>unlocked 상태</p>
<p>  mutex가 잠금 해제된 상태, 다른 thread가 mutex를 잠글 수 있게 됨</p>
</li>
</ul>
<pre><code>std::mutex global_mutex;

void inc(int* output)
{
    global_mutex.lock;
    (*output)++; //critical section
    global_mutex.unlock(); //FORGOT TO UNLOCK?
}

void worker(int* input, int start, 
int size, int* output)
{
    for(int i=0;i&lt;size;i++) {
        if(input[start+i]==0) {
            inc(output);
        }
    }
}</code></pre><p>unlock하는 것을 잊는 등, mutex를 부주의하게 사용하면 <strong>deadlock</strong> 상황이 발생할 수 있음</p>
<p>모든 thread가 mutex를 얻지 못해 무한정 기다리는 상황이 발생하여 더 이상 진행이 불가 ,,</p>
<pre><code>std::mutex global_mutex;

void inc(int* output)
{
    std::lock_guard&lt;std::mutex&gt; guard(global_mutex);
    (*output)++;
}

void worker(int* input, int start, 
int size, int* output)
{
    for(int i=0;i&lt;size;i++) {
        if(input[start+i]==0) {
            inc(output);
        }
    }
}</code></pre><ul>
<li><p>lock_guard</p>
<p>  RAII(Resource Acquistion Is Initialization) 기법을 활용하여 뮤텍스를 안전하게 관리</p>
<p>  뮤텍스를 자동으로 잠그는 객체</p>
<ul>
<li>객체가 생성될 때 뮤텍스를 잠금 상태로 만듦</li>
<li>객체가 소멸될 때 뮤텍스를 자동으로 해제</li>
<li>lock_guard 객체의 생성자에서 뮤텍스를 잠그고, 소멸자에서 뮤텍스를 해제</li>
</ul>
</li>
<li><p>RAII (Resource Acquistion Is Initialization)</p>
<ul>
<li><p>&quot;자원 할당은 초기화(Initialization)의 책임을 갖는다”</p>
</li>
<li><p>객체의 생성자에서 자원을 할당하고, 소멸자에서 자원을 해제하여 자원 누수를 방지</p>
</li>
<li><p>ex. Using RAII for thread join</p>
<pre><code>class thread_guard
{
std::thread&amp; t;
public:

  thread_guard(std::thread&amp; t_):t(t_)
  {}

  ~thread_guard()
  {
      if(t.joinable())
      {
          t.join();
      }
  }
  thread_guard(thread_guard const&amp;) = delete; //del copy constructor
  thread_guard&amp; operator=(thread_guard const&amp;) = delete; // del copy operator
};</code></pre><h2 id="ෆ-deadlock-교착상태">ෆ Deadlock 교착상태</h2>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>thread 혹은 process가 자원을 얻지 못해 다음 처리를 하지 못하는 상태
시스템적으로 한정된 자원을 여러 곳에서 사용하려고 할 때 발생</p>
</blockquote>
<ul>
<li><p>다음 4가지 상황이 동시에 성립할 때 발생</p>
<ol>
<li><p>상호 배제 (Mutual exclustion)</p>
<p> : 자원은 한 번에 하나만 사용가능</p>
</li>
<li><p>점유 대기 (Hold and wait)</p>
<p> : 최소한 하나의 자원을 점유하고 있으면서 다른 process에 할당되어 사용하고 있는 자원을 추가로 점유하기 위해 대기하는 process가 있어야 함</p>
</li>
<li><p>비선점 (No preemption)</p>
<p> : 다른 process에 할당된 자원은 사용이 끝날 때까지 강제로 빼앗을 수 없음</p>
</li>
<li><p>순환 대기 (Circular wait)</p>
<p> : process의 집합 {P0, P1, P2, … ,Pn} 에서 P0은 P1이 점유한 자원을 점유하기 위해 대기하고 P1은 P2가 점유한 자원을 점유하기 위해 대기하고 … Pn-1은 Pn이 점유한 자원을 점유하기 위해 대기하며 Pn은 P0가 점유한 자원을 요구해야 함</p>
<pre><code>class int_wrapper
{
public:
 int_wrapper(int val):val(val){}
 std::mutex m;
 int val;
};
void swap(int_wrapper&amp; v1,
                     int_wrapper&amp; v2)
{
v1.m.lock();
v2.m.lock();
int tmp = v1.val;
v1.val = v2.val;
v2.val = tmp;
v1.m.unlock();
v2.m.unlock();
}
</code></pre></li>
</ol>
</li>
</ul>
<p>int main()
{
    int_wrapper a(0);
    int_wrapper x(1);</p>
<pre><code>for(int i=0;i&lt;10000;i++) {
    std::cout&lt;&lt;&quot;start iteration &quot;&lt;&lt;i&lt;&lt;std::endl;
    std::thread t1(swap, std::ref(x), std::ref(a)) ;
    std::thread t2(swap, std::ref(a), std::ref(x)) ;

    t1.join();
    t2.join();
    std::cout&lt;&lt;&quot;done a: &quot;&lt;&lt;a.val&lt;&lt;&quot;, x:
    &quot;&lt;&lt;x.val&lt;&lt;std::endl;
}
    return 0;</code></pre><p>}</p>
<pre><code>![](https://velog.velcdn.com/images/ii_seo/post/add31dd8-f274-4f1d-9829-d9000d6a62ab/image.png)
- 두 개의 mutex를 가지고 있음
- 각 mutex를 잠근 후 다른 mutex를 잠그는 swap 함수 정의
- swap함수를 두 thread에서 반복적으로 호출 → deadlock 발생

→ deadlock이 발생하는 이유 ?

- 각 thread가 서로 다른 순서로 mutex를 잠그기 때문,,
- 이러한 상황에서 두 thread는 서로가 가진 mutex를 얻지 못해 무한정 기다리게 되어 deadlock이 발생!
### 해결방법 ?

1. lock multiple mutexes

    `std::lock` 을 사용해 여러 개의 mutex를 한 번에 lock

    → 모든 Mutex를 안전하게 lock 가능, deadlock의 위험을 피할 수 있음</code></pre><p>void swap(int_wrapper&amp; v1,
int_wrapper&amp; v2)
{
    std::lock(v1.m, v2.m);
    int tmp = v1.val;
    v1.val = v2.val;
    v2.val = tmp;
    v1.m.unlock();
    v2.m.unlock();
}</p>
<pre><code>2. use lock_guard, adopt_lock

    `std::lock`을 사용해 두 개의 mutex를 한 번에 잠그고, 이를 `lock_guard`나 `unique_lock` 객체에 `adopt_lock` 옵션을 주어 넘겨줄 수 있음

    - `adopt_lock` : 이미 잠겨 있는 mutex를 전달할 수 있게 해줌

        → 두 mutex를 안전하게 잠그고 데드락을 피할 수 있음</code></pre><p>class int_wrapper
{
    public:
        int_wrapper(int val):val(val){}
        std::mutex m;
        int val;
};
void swap(int_wrapper&amp; v1, int_wrapper&amp; v2)
{
    std::lock(v1.m, v2.m);
    std::lock_guard<a href="std::mutex">std::mutex</a> lock_v1(v1.m, std::adopt_lock);
    std::lock_guard<a href="std::mutex">std::mutex</a> lock_v2(v2.m, std::adopt_lock);
    int tmp = v1.val;
    v1.val = v2.val;
    v2.val = tmp;
}</p>
<pre><code>## ෆ Wait
![](https://velog.velcdn.com/images/ii_seo/post/6d48fe02-8f1e-4b0d-823b-3262c4f687a2/image.png)producer-consumer 문제

: multi-thread 환경에서 공유 자원인 queue를 여러 producer thread가 data를 추가하고, 여러 consumer thread가 data를 소비하는 상황

→ producer thread가 data를 queue에 추가할 때 queue가 가득 차 있는 경우를 처리

→ consumer thread가 queue에서 data를 가져올 때 queue가 비어 있는 경우를 처리

### 1. busy wait

&gt; 자원을 얻기 위해 기다리는 것이 아닌 권한을 얻기 위해 기다리는 것
&gt;
</code></pre><p>#include <iostream>
#include <thread>
#include <queue>
#include <mutex></p>
<p>std::mutex m;</p>
<p>std::queue<int> shared_queue;
const int N = 10000;
void produce()
{
    for(int i=0;i&lt;N;i++) {
    m.lock();
    std::cout&lt;&lt;&quot;i produce&quot;&lt;&lt;i&lt;&lt;std::endl;
    shared_queue.push(i);
    m.unlock();</p>
<p>//1sec artificial delay
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
}
void busy_consume()
{
    for(int i=0;i&lt;N;i++) {
        while(shared_queue.empty()) {
            m.unlock();
            m.lock();
        }
        std::cout&lt;&lt;&quot;i read &quot;
                    &lt;&lt;shared_queue.front()&lt;&lt;std::endl;
        shared_queue.pop();
    }
}
int main()
{
    std::thread t1(produce);
    std::thread t2(busy_consume);
    t1.join();
    t2.join();
    return 0;
}</p>
<pre><code>- consumer thread가 계속해서 queue가 비어있는지 확인
- 그러나 queue가 비어있는 경우에도 thread가 계속해서 CPU를 점유하며 queue가 채워질 때까지 대기함

    → CPU 자원 낭비, 비효율적


### 2. busy wait + sleep

- Sleeping

&gt; 권한을 얻기 위해 걸리는 시간을 wait
queue에 실행중인 Thread 정보를 담고 다른 Thread에게 CPU를 양보하는 것
&gt;
</code></pre><p>#include <iostream>
#include <thread>
#include <queue>
#include <mutex></p>
<p>std::mutex m;</p>
<p>std::queue<int> shared_queue;
const int N = 10000;
void produce()
{
    for(int i=0;i&lt;N;i++) {
    m.lock();
    std::cout&lt;&lt;&quot;i produce&quot;&lt;&lt;i&lt;&lt;std::endl;
    shared_queue.push(i);
    m.unlock();</p>
<p>//1sec artificial delay
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
}
void sleep_consume()
{
    for(int i=0;i&lt;N;i++) {
        while(shared_queue.empty()) {
            m.unlock();
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            m.lock();
        }
        std::cout&lt;&lt;&quot;i read &quot;
                    &lt;&lt;shared_queue.front()&lt;&lt;std::endl;
        shared_queue.pop();
    }
}
int main()
{
    std::thread t1(produce);
    std::thread t2(busy_consume);
    t1.join();
    t2.join();
    return 0;
}</p>
<pre><code>- consumer thread가 queue가 비어 있는 경우 일정 시간동안 sleep
- 그 후에 다시 queue가 비어 있는지 확인

    → consumer thread가 더 많은 CPU자원을 점유하지 않음

    → queue가 채워질 때까지 기다리는데 도움이 됨

    → but 여전히 일정 시간마다 불필요한 check가 필요 ,,


### 3. condition variables</code></pre><p>#include <iostream>
#include <thread>
#include <condition_variable>
#include <queue>
#include <mutex></p>
<p>std::mutex m;</p>
<p>std::queue<int> shared_queue;
const int N = 10000;
std::condition_variable cond;
void produce()
{
    for(int i=0;i&lt;N;i++) {
        std::unique_lock<a href="std::mutex">std::mutex</a> lock(m);
        std::cout&lt;&lt;&quot;i produce&quot;&lt;&lt;i&lt;&lt;std::endl;
        shared_queue.push(i);
        cond.notify_one();
        lock.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    }
}
void consume()
{
    for(int i=0;i&lt;N;i++) {
        std::unique_lock<a href="std::mutex">std::mutex</a> lock(m);
        cond.wait(lock,[]{return !shared_queue.empty();});
        std::cout&lt;&lt;&quot;i read&quot;&lt;&lt;shared_queue.front()&lt;&lt;std::endl;
        shared_queue.pop();
        lock.unlock();
    }
}
int main()
{
    std::thread t1(produce);
    std::thread t2(consume);
    t1.join();
    t2.join();
    return 0;
}</p>
<pre><code>- queue가 비어 있는 경우 소비자 thread 대기
- condition variables
    - thread가 특정 조건을 만족할 때까지 대기하도록 함
    - 다른 Thread가 조건을 만족시키면 대기 중인 thread 깨움
    - consumer thread : queue가 비어 있는 동안 조건 변수를 기다림
    - producer thread : 데이터를 queue에 추가한 후 condition variable을 통해 consumer thread를 깨움
- `cond.wait(unique_lock, predicate function)`

    : condition variable을 기다리는 데에 사용

    - `unique_lock`
        - `lock_guard`와 비슷한 용도
        - 사용자가 수동으로 잠금 및 잠금 해제 가능 (`lock()`, `unlock()`)
        - 생성 시 뮤텍스를 잠그거나 해제 가능
    - `predicate function`
        - 조건이 만족되는지 확인하는 함수
        - 조건 함수가 True : thread는 대기 중인 상태를 벗어나 다음 코드를 실행
        - 조건 함수가 False : thread는 대기 상태에 남음, 나중에 조건이 충족될 때까지 기다림
- `notify_one()`
    - 대기 중인 thread 중 하나를 깨우는 역할
    - 조건 변수를 기다리는 thread 중 하나가 신호를 받아 실행 재개
- `notify_all()`
    - 모든 대기 중인 thread 깨움
    - 여러 thread가 동시에 조건 변수를 기다리고 있을 때 사용

## ෆ Atomics

&gt; 동시에 여러 thread에서 공유되는 변수의 안전한 업데이트를 가능하게 하는 기술
&gt; 

→ thread간 동기화가 필요할 때 사용</code></pre><p>#include <iostream>
#include <thread>
#include <vector>
#include <atomic>
#include &lt;assert.h&gt;</p>
<p>std::atomic<int> output</p>
<p>void worker(int* input, int start, int size)
{
    for(int i=0;i&lt;size;i++) {
        if(input[start+i]==0) {
            output+=1;
        }
    }
}</p>
<pre><code>- All-Or-Nothing : 연산이 완전히 수행되거나, 전혀 수행되지 않아야 함
- 원자적 연산을 제공 → 여러 thread에서 동시에 접근해도 데이터의 일관성을 보장한다는 뜻
- 데이터의 일관성을 보장하기 위한 동기화 과정 때문에 일반적인 연산에 비해 비용이 더 들 수 있다..
- 또한 여러 연산이 함께 동작해야 하는 복잡한 동기화 작업에는 lock과 같은 더 고수준의 동기화 도구가 필요 ..

## ෆ Barrier
![](https://velog.velcdn.com/images/ii_seo/post/30cf9f94-f338-4da4-8924-740a0e1f030b/image.png)
- 전역 동기화(global synchronization)의 한 형태
- 모든 thread가 특정 지점에서 멈추고, 다른 모든 thread가 그 지점에 도달할 때까지 대기하는 동작

    → 여러 thread간의 작업을 동기화할 때 유용</code></pre><p>#include <iostream>
#include &lt;boost/thread/barrier.hpp&gt;
#include <thread>
#include <vector></p>
<p>void worker(int* input, int start, int size, int* output, boost::barrier&amp; bar)
{
    for(int i=0;i&lt;size;i++) {
        if(input[start+i]==0) {
            (*output)++;
        }
        if(i%1000==0) {
            bar.wait();
            std::cout&lt;&lt;&quot;thread starting at &quot;&lt;&lt;start&lt;&lt;&quot; passed bar at i=&quot;&lt;&lt;i&lt;&lt;std::endl;
        }
    }
}</p>
<p>int main(int argc, char* argv[])
{
    const int N = atoi(argv[1]);
    const int NT = atoi(argv[2]);
    boost::barrier bar(NT);
    for(int t=0;t&lt;NT;t++) {
        threads.push_back(std::thread(worker, array, start, size, &amp;count, std::ref(bar)));
    }
    …
}</p>
<pre><code># ❦ Thread-safety

&gt; 여러 thread가 동시에 코드를 실행할 때 공유된 데이터를 안전하게 조작하는 능력
→ 여러 thread에서 동시에 실행되어도 예상대로 작동, 데이터 무결성을 보존
&gt; 
- 가장 쉽게 thread-safe한 코드를 만드는 법 → add a lock to it

## ෆ Thread-safe queue</code></pre><p>template<typename T>
class thread_safe_queue
{
    std::queue<T> _queue;
    std::mutex mtx;
    public:</p>
<pre><code>thread_safe_queue()
{}

void push(T value)
{
    std::lock_guard&lt;std::mutex&gt; lock(mtx);
    _queue.push(value);
}

T pop()
{
    std::lock_guard&lt;std::mutex&gt; lock(mtx);
    T res = _queue.front();
    _queue.pop();
    return res;
}</code></pre><p>};</p>
<pre><code>→ safe, but inefficient

- queue에 대한 모든 조작(push,pop ..)이 하나의 mutex로 보호

    → queue에 대한 동시 접근을 제어

    → 데이터의 일관성 보장

    → but, queue에 대한 조작이 가능한 thread 수를 제한

    - 뮤텍스는 크리티컬 섹션(critical section)을 보호하기 위해 사용되며, 크리티컬 섹션을 보호하는 동안 다른 스레드는 대기

        → 한 번에 하나의 thread만 queue에 접근

        → 병목현상
## ෆ Linked-list queue</code></pre><p>template<typename T>
class linked_list_queue
{
    class node
    {
        public:
        node(T init_value):value(init_value)
        {}
        T value;
        node* next;
    };</p>
<pre><code>node* head;
node* tail;
public:

linked_list_queue()
{
    head = tail = new node(0); //dummy node
}
void push(T value)
{
    node* tmp = new node(value);
    tmp-&gt;value = value;
    tmp-&gt;next = nullptr;
    tail-&gt;next = tmp;
    tail = tmp;
}

bool pop(T&amp; value)
{
    node* old_head = head;
    node* new_head = old_head-&gt;next;
    if(new_head == nullptr) {
        return false;
}
value = new_head-&gt;value;
head = new_head;
delete old_head;

return true;
}</code></pre><p>};</p>
<pre><code>→ 모든 조작에 대해 mutex를 사용할 필요가 없음

1. `push()`

    tail을 사용하여 새 node를 queue에 추가

    → tail을 변경하는 부분만 lock

2. `pop()`

    head를 사용하여 첫 번째 node를 queue에서 제거

    → head를 변경하는 부분만 lock


⇒ 각각 tail과 head만 잠그면 ok

서로 다른 thread가 동시에 queue에 접근 가능, 성능도 향상
## ෆ Thread-safe Linked-list queue</code></pre><p>template<typename T>
class safe_llq
{
    class node
    {
        public:
        node(T init_value):value(init_value)
        {}
        T value;
        node* next;
    };
    node* head;
    node* tail;
    std::mutex head_mtx;
    std::mutex tail_mtx;
    public:</p>
<pre><code>safe_llq()
{
    head = tail = new node(0); //dummy node
}

void push(T value)
{
    node* tmp = new node(value);
    tmp-&gt;value = value;
    tmp-&gt;next = nullptr;
    std::lock_guard&lt;std::mutex&gt; tail_lock(tail_mtx);
    tail-&gt;next = tmp;
    tail = tmp;
}

bool pop(T&amp; value)
{
    std::lock_guard&lt;std::mutex&gt; head_lock(head_mtx);
    node* old_head = head;
    node* new_head = old_head-&gt;next;
    if(new_head == nullptr) {
        return false;
    }
    value = new_head-&gt;value;
    head = new_head;
    delete old_head;

    return true;
}</code></pre><p>};</p>
<p>```</p>
<ul>
<li>두 가지 mutex를 사용하여 두 가지 종류의 임계 영역 보호</li>
</ul>
<ol>
<li><p><code>tail_mtx</code></p>
<p> <code>push()</code> 함수에서 새로운 값을 queue에 추가할 때 tail update</p>
<p> → tail을 잠그고 새로운 노드 추가</p>
</li>
<li><p><code>head_mtx</code></p>
<p> <code>pop()</code> 함수에서 첫 번째 값을 queue에서 제거할 때 head update</p>
<p> → head를 잠그고 첫 번째 노드 제거</p>
</li>
</ol>
<ul>
<li><code>push()</code>, <code>pop()</code> 함수가 동시에 발생 가능</li>
<li>각각의 함수는 서로 다른 mutex를 사용하여 자신의 임계 영역 보호</li>
</ul>
<p><strong>⇒ fine-grained locking !</strong></p>
<ol>
<li>Lock entire list
<img src="https://velog.velcdn.com/images/ii_seo/post/322e4ea0-fd00-4439-9657-3a6d8a9c72fa/image.png" alt=""></li>
</ol>
<ul>
<li>queue의 모든 작업 (push / pop)이 하나의 mutex로 보호</li>
<li>queue를 변경할 때마다 전체 list가 잠김
→ 간단하고 구현이 쉬우나, 동시성이 낮아지고 병목 현상이 발생</li>
</ul>
<ol start="2">
<li>Node-by-Node lock
<img src="https://velog.velcdn.com/images/ii_seo/post/5572eb30-740c-45fe-ae30-83855b93ef7b/image.png" alt=""></li>
</ol>
<ul>
<li>각 node에 대해 개별적인 mutex를 사용하여 각 node를 개별적으로 보호</li>
<li>더 많은 동시성을 제공하지만, error 발생 가능
<img src="https://velog.velcdn.com/images/ii_seo/post/41b15dea-e2e6-44ce-a4a0-90a603bfc082/image.png" alt=""></li>
</ul>
<ol start="3">
<li>Hand-over-Hand locking
<img src="https://velog.velcdn.com/images/ii_seo/post/bbc7fff3-8e81-4a6d-b7cc-2dd7863aef81/image.png" alt=""></li>
</ol>
<ul>
<li>노드 사이에서 잠금을 전달</li>
<li>첫 번째 node를 잠근 다음, 두 번째 node를 잠금 …</li>
<li>구현이 복잡하고 잠금의 overhead 증가</li>
</ul>
<h2 id="ෆ-thread-safe-hash-table">ෆ Thread-safe Hash Table</h2>
<ul>
<li>아마도 가장 빠른 data structure ,,<ul>
<li>O(1) : write, read</li>
</ul>
</li>
</ul>
<ol>
<li>global lock
<img src="https://velog.velcdn.com/images/ii_seo/post/01bae2bf-09e9-4d07-82a7-e0be81f40af2/image.png" alt=""></li>
</ol>
<ul>
<li>hash table 전체에 대해 하나의 전역 락(mutex)를 사용</li>
<li>구현이 간단하고, 이해하기 쉽지만, 동시성이 낮고 병목 현상이 발생할 수 있음</li>
</ul>
<ol start="2">
<li><p>fine-grained lock
 <img src="https://velog.velcdn.com/images/ii_seo/post/94132dbd-246e-4a72-8f18-ba3eff571bef/image.png" alt=""></p>
<ul>
<li>hash table의 각 버킷에 대해 개별적인 락(mutex) 사용</li>
<li>각 버킷에 대한 작업은 해당 버킷에 대한 락을 획득<ul>
<li>서로 다른 버킷에 대한 작업은 서로에게 영향을 주지 않아 더 많은 동시성 달성 가능</li>
</ul>
</li>
<li>락 경합이 발생할 수 있음<ul>
<li>두 thread가 동일한 버킷을 수정하려고 시도할 때 발생하는 경합 ,,</li>
</ul>
</li>
<li>버킷의 개수가 많아지면 overhead 발생<ul>
<li>O(B) : B는 버킷의 수</li>
</ul>
</li>
</ul>
</li>
<li><p>lock striping
<img src="https://velog.velcdn.com/images/ii_seo/post/e74374da-9b6e-4ea0-82b4-1ca7b3024dea/image.png" alt=""></p>
<ul>
<li>hash table을 여러 개의 더 작은 섹션 또는 스트라이프로 분할</li>
<li>각 섹션에 대해 개별적인 락 사용</li>
<li>락 경합을 줄이고 동시성을 높일 수 있음<ul>
<li>but 스트라이핑을 어떻게 구현하느냐에 따라 락 경합이 발생 가능</li>
</ul>
</li>
</ul>
</li>
</ol>
<h2 id="ෆ-other-features-to-look-at">ෆ Other features to look at</h2>
<ul>
<li><code>std::this_thread::get_id()</code><ul>
<li>현재 thread의 식별자를 나타내는 <code>std::thread::id</code> 객체를 반환</li>
<li>디버깅이나 로깅과 같은 목적으로 사용, 실행 중에 개별 thread를 식별하는데 유용</li>
</ul>
</li>
<li><code>std::move()</code><ul>
<li>lvalue를 rvalue로 변환</li>
<li>자원의 소유권을 전달하는 등의 상황에서 자주 사용<ul>
<li>데이터 구조의 소유권을 한 thread에서 다른 thread로 전달 ,,</li>
</ul>
</li>
<li><code>std::thread</code>와 함께 사용되면 객체의 소유권을 thread에게 전달 가능</li>
</ul>
</li>
<li><code>std::thread::hardware_concurrency()</code><ul>
<li>시스템이 지원하는 hardware thread의 수를 반환</li>
<li>동시에 실행 가능한 thread의 수에 대한 힌트를 제공<ul>
<li>성능을 저하시키지 않고 동시성 thread를 실행할 수 있는 최적의 thread 수를 결정</li>
</ul>
</li>
<li>최적의 thread 수를 결정하는데 자주 사용</li>
</ul>
</li>
<li><code>std::async</code>, <code>std::promise</code>, <code>std::future</code><ul>
<li><code>std::async</code> : 비동기 작업 실행을 위한 고수준 interface, 계산 결가를 나타내는 <code>std::future</code> 객체 반환</li>
<li><code>std::promise</code> : 값이나 예외를 비동기적으로 저장하기 위해 사용, 이후 <code>std::future</code> 객체를 통해 비동기적으로 사용 가능</li>
<li><code>std::future</code> : 아직 사용 가능하지 않은 값, 나중에 사용 가능하게 됨</li>
</ul>
</li>
<li>most vexing parse problem
<img src="https://velog.velcdn.com/images/ii_seo/post/e77feea6-2aa4-4181-aa76-d6b0ecdc520f/image.png" alt=""><ul>
<li>괄호를 사용하여 <code>nop_object</code>의 인스턴스를 생성</li>
<li>이를 <code>std::thread</code> 생성자에 전달하려고 시도<ul>
<li>하지만, 이 코드는 가장 먼저 &quot;가장 광범위한 해석&quot; 규칙(most vexing parse)에 의해 문제가 발생</li>
<li>컴파일러는 이것을 함수 선언으로 해석할 수 있으며, 결과적으로 <code>t3</code>를 <code>nop_object</code> 타입의 파라미터를 받고 반환 타입이 <code>std::thread</code>인 함수 선언으로 오해</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>  ⇒ 해결 방법<img src="https://velog.velcdn.com/images/ii_seo/post/369cfd5f-91b4-42b5-909e-971cf88564e3/image.png" alt="">: use {} instead of ()</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MGP] 02-1A. Thread Programming_Programming model]]></title>
            <link>https://velog.io/@ii_seo/MGP-02-1.-Thread-Programming</link>
            <guid>https://velog.io/@ii_seo/MGP-02-1.-Thread-Programming</guid>
            <pubDate>Mon, 06 May 2024 10:33:23 GMT</pubDate>
            <description><![CDATA[<h1 id="❦-programming-model">❦ Programming model</h1>
<p>Parallel programming에서 programming model은 프로그래머에게 <strong>communication abstraction</strong>을 제공</p>
<p>→ 프로그래머가 컴퓨터 시스템의 병렬성을 이해하고 활용할 수 있게 도와줌</p>
<ul>
<li>Shared Memory Model<ul>
<li>모든 process/thread가 동일한 주소 공간을 공유<ul>
<li>데이터를 공유하는 것이 가능</li>
<li>process/thread간의 communication은 주로 공유된 변수 또는 데이터 구조를 통해 이루어짐</li>
</ul>
</li>
<li>대부분의 multi processor system/ multi-core processor에서 사용<ul>
<li>ex. OpenMP : Shared Memory Model을 기반으로 구현되어 있음</li>
</ul>
</li>
<li>프로세스 또는 스레드 간의 데이터 공유를 편리하게 처리</li>
<li>데이터의 일관성과 동기화에 대한 관리가 필요</li>
</ul>
</li>
<li>Message Passing Model<ul>
<li>process간 communication을 위해 명시적으로 message를 주고 받는 방식<ul>
<li>각 process는 자체적으로 독립적인 주소 공간을 가짐</li>
</ul>
</li>
<li>분산 시스템에서 주로 사용<ul>
<li>대규모 cluster 또는 분산 컴퓨팅 환경에서 process간 통신이 필요한 경우 유용</li>
<li>ex. MPI (메시지 패싱 라이브러리)</li>
</ul>
</li>
<li>명시적인 통신을 통해 분산 환경에서의 효과적인 상호 작용</li>
<li>통신 비용과 병목 현상에 대한 관리가 필요</li>
</ul>
</li>
</ul>
<h2 id="ෆ-shared-memory-model">ෆ Shared memory model</h2>
<h3 id="ಌ-memory">ಌ Memory</h3>
<blockquote>
<p><strong>address</strong>를 통해서 접근(읽기 및 쓰기)할 수 있는 byte의 집합</p>
</blockquote>
<p>데이터와 명령어를 저장하고 접근하는 장치
<img src="https://velog.velcdn.com/images/ii_seo/post/eacbd213-6fb5-4b9d-b54b-9c9832019643/image.png" alt=""></p>
<ul>
<li>0x8 에 32가 저장되어 있음</li>
<li>0x10에 128이 저장되어 있음</li>
<li>주로 byte 단위로 주소 지정이 이루어짐</li>
</ul>
<h3 id="⇒-cpu와-memory의-구조는">⇒ CPU와 Memory의 구조는?</h3>
<ul>
<li><p>Von Neumann 아키텍쳐
<img src="https://velog.velcdn.com/images/ii_seo/post/261e3341-7215-4707-b842-33b3e06d2ebf/image.png" alt=""></p>
<ol>
<li><p>Single data bus</p>
<p>CPU와 memory간에 단일 데이터 버스가 존재</p>
<p>→ 이 bus를 통해 CPU는 memory로부터 명령어와 데이터를 읽거나 쓸 수 있음</p>
</li>
<li><p>Single Storage</p>
<p>프로그램의 명령어와 데이터는 동일한 메모리에 저장</p>
<p>프로그램의 구조를 단순화하고 프로그램의 유연성을 높임</p>
</li>
<li><p>Instruction and Data Separation</p>
<p>프로그램 명령어와 데이터는 동일한 메모리에 저장되나, CPU는 명령어와 데이터를 구분하여 처리</p>
<p>CPU는 PC를 사용하여 메모리에서 다음에 실행할 명령어의 위치를 추적</p>
</li>
<li><p>Sequential Instruction Execution</p>
<p>프로그램은 순차적으로 실행</p>
<p>CPU는 메모리에서 한 번에 하나의 명령어를 가져와 실행</p>
<p>→ 프로그래밍의 단순성을 유지하고 복잡성을 줄임</p>
</li>
</ol>
</li>
<li><p>그럼 multi-core CPU는 ??
<img src="https://velog.velcdn.com/images/ii_seo/post/d21d23d5-289a-4bc5-93a7-b6fb7833dc87/image.png" alt=""></p>
<h3 id="⇒-하지만-실제로는---memory-hierarchy">⇒ 하지만 실제로는 ,, : Memory hierarchy</h3>
<p><img src="https://velog.velcdn.com/images/ii_seo/post/5f78cc16-3377-4217-90a4-8206f9483909/image.png" alt="">
상위 계층 : 일반적으로 빠른 접근 속도 제공
하위 계층 : 더 큰 용량을 제공</p>
</li>
</ul>
<ol>
<li>Register
 : cache memory에서 검색된 단어를 저장</li>
<li>Cache<ol>
<li>L1 : L2 cache에서 검색된 cache line을 저장</li>
<li>L2 : L3 cache에서 검색된 cache line을 저장</li>
<li>L3 : memory에서 검색된 cache line을 저장</li>
</ol>
</li>
<li>Main memory
 : local disk에서 검색된 disk block을 저장</li>
<li>Secondary Storage
 : 원격 network server의 disk에서 검색된 file을 저장</li>
</ol>
<h3 id="ಌ-shared-address-space">ಌ Shared address space</h3>
<ul>
<li><p>각 thread는 동일한 process 내에서 실행</p>
</li>
<li><p>memory에 접근 시 동일한 address space를 공유</p>
<p>  → 여러 thread가 동일한 address space를 공유하면 하나의 thread가 해당 address에 쓴 내용을 다른 thread가 볼 수 있음</p>
</li>
<li><p>한 thread가 address space에 값을 쓰면, 이 값이 다른 모든 thread에게도 즉시 반영</p>
</li>
<li><p>mutual exclusion (상호 배제)를 필요로 함</p>
<ul>
<li><p>여러 thread가 동시에 memory에 access 하는 것을 방지</p>
<p>  → data의 일관성과 무결성 보장</p>
</li>
</ul>
</li>
<li><p>Hardware적 지원이 필요</p>
<ul>
<li>core간의 interconnect</li>
<li>시스템이 확장되면서 발생 가능한 확장성 문제</li>
<li>캐시 일관성</li>
<li>…
<img src="https://velog.velcdn.com/images/ii_seo/post/897bbd61-0f04-4900-9ce7-3df355b2a7ea/image.png" alt=""></li>
</ul>
</li>
</ul>
<ol>
<li>“Dance-hall” organization
<img src="https://velog.velcdn.com/images/ii_seo/post/5fc0b23f-e4b9-4daa-8b19-8dc0550eee38/image.png" alt="">
여러 스레드가 자유롭게 데이터를 교환하고 공유</li>
</ol>
<ul>
<li>메모리 주소 공간의 각 부분이 다수의 스레드에 의해 공유</li>
<li>스레드 간의 데이터 공유와 통신이 쉽게 이루어질 수 있도록 설계</li>
</ul>
<ol start="2">
<li>Interconnect examples
<img src="https://velog.velcdn.com/images/ii_seo/post/1c18b6ea-5bff-47d7-8afc-4bcf3366ba0b/image.png" alt=""></li>
</ol>
<ul>
<li>스레드 간 통신을 위한 인터커넥트(Interconnect) 구조</li>
<li>인터커넥트는 다수의 프로세서 또는 코어 간의 통신을 지원하기 위한 통신 경로 및 프로토콜을 제공</li>
<li>인터커넥트는 스레드 간의 데이터 전송과 동기화를 지원하며, 고성능 및 효율적인 데이터 교환을 가능</li>
<li>ex 1 : intel core i7 (Kaby Lake)<img src="https://velog.velcdn.com/images/ii_seo/post/be5d3f7e-f4a5-4b85-aa2f-cfef4d05544c/image.png" alt=""></li>
<li>ex 2 : sun Niagara 2
<img src="https://velog.velcdn.com/images/ii_seo/post/870d71f9-9437-4cbc-97ea-0d2bf1893656/image.png" alt=""></li>
</ul>
<h3 id="ಌ-shared-memory-uma-uniform-memory-access">ಌ Shared memory UMA (Uniform Memory Access)</h3>
<ul>
<li>균일 기억 장치 접근</li>
<li>모든 processor들이 상호간에 연결되어 하나의 메모리를 공유하는 기술</li>
<li>processor들은 memory의 어느 영역 이던지 접근이 가능하며, 모든 processor가 걸리는 시간이 <strong>동일</strong></li>
<li>구조가 간단하고, 프로그래밍 하기는 쉬우나, 메모리에 한번에 하나씩의 연결만 가능하여, 커지면 커질수록 효율성이 떨어짐</li>
</ul>
<h3 id="ಌ-shared-memory-numa-non-uniform-memory-access">ಌ Shared memory NUMA (Non-Uniform Memory Access)</h3>
<ul>
<li><p>메모리에 접근하는 시간이 processor와 memory의 상대적인 위치에 따라 <strong>달라짐</strong></p>
<ul>
<li><p>local memory access (로컬 메모리에 접근) vs remote memory access (원격 메모리 접근)</p>
<p>  → 서로 다른 지연 시간을 가짐</p>
<ul>
<li><p>local memory access</p>
<p>  : 각각의 CPU마다 memory를 가지고 있는 구조에서 memory에 접근하는 경우</p>
</li>
<li><p>remote memory access</p>
<p>  : CPU와 memory를 합쳐 node를 구성 → 자신의 memory가 아닌 다른 node의 memory 접근하는 경우</p>
</li>
</ul>
</li>
</ul>
</li>
<li><p>물리적인 interconnects로 인해 발생하는 지연(latency)및 대역폭(bandwidth) 불균형</p>
<ul>
<li><p>성능 저하의 원인</p>
</li>
<li><p>동일한 주소 공간 내에서 발생</p>
<p>⇒ <strong>first touch</strong> 정책</p>
</li>
<li><p>memory가 처음으로 access 될 때 해당 memory를 local memory에 할당하는 정책</p>
</li>
<li><p>각 processor가 가능한 한 local memory에 access하여 지연 시간과 대역폭 불균형을 최소화</p>
<p>⇒ 메모리 할당을 수동으로 제어 : <strong>munactl 유틸리티</strong></p>
</li>
<li><p>-membind: 메모리를 할당할 노드를 정의합니다.</p>
</li>
<li><p>-cpunodebind: 사용할 NUMA 노드를 지정합니다.</p>
</li>
<li><p>-physcpubind: 사용할 CPU를 선언합니다.</p>
</li>
<li><p>-interleave: 메모리 할당에 대한 interleaving 정책을 사용합니다.</p>
</li>
<li><p>→ 메모리 및 CPU 할당을 세밀하게 제어하여 성능을 최적화</p>
</li>
</ul>
</li>
<li><p>ex. modern dual socket machine<img src="https://velog.velcdn.com/images/ii_seo/post/f0fb871e-e5b9-4154-be68-7c5c9b74f147/image.png" alt="">core 6에서 data x에 접근해야 한다면 ,,
sol 1 : data x를 Memory 1에 위치하게 함
sol 2 : core 6에서 하는 일을 core 1이 하게끔 함</p>
</li>
<li><p>thread migration
  : processor가 thread를 중지하고 현재 상태 저장 → 다른 core에서 thread를 다시 시작 → 이전 상태 복원</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[MGP] 01. Basic Parallel Architectures]]></title>
            <link>https://velog.io/@ii_seo/MGP-01.-Basic-Parallel-Architectures</link>
            <guid>https://velog.io/@ii_seo/MGP-01.-Basic-Parallel-Architectures</guid>
            <pubDate>Sun, 05 May 2024 18:39:06 GMT</pubDate>
            <description><![CDATA[<h1 id="❦-superscalar-processors-sisd-single-instruction-single-data">❦ Superscalar processors (SISD, Single Instruction Single Data)</h1>
<h2 id="ෆ-ilp">ෆ ILP</h2>
<p>보통 instruction은 sequential 하게 나열 → 순서대로 하나씩 실행 시 overhead ↑</p>
<p>→ 두 개 이상의 instruction을 ‘동시에’ 실행하려면 ?</p>
<ol>
<li>서로 independent한 instruction들을 찾아서 parallel(병렬)하게 실행시킬 수 있다면 overhead ↓ 가능<ul>
<li>Superscalar<ul>
<li>1 cycle 동안에 서로 다른 independent한 2개의 instruction을 동시에 수행</li>
</ul>
</li>
</ul>
</li>
<li>완전히 중첩시키지는 않더라도 pipeline과 같이 stage를 약간씩 중첩시켜서 실행 가능<ul>
<li>Superpipeline<ul>
<li>한 클럭을 2개로 나누고, 나누어진 클럭에서 각각 서로 다른 연산을 수행</li>
</ul>
</li>
</ul>
</li>
<li>동시에 수행</li>
</ol>
<h3 id="⇒-ilp-방식">⇒ ILP 방식</h3>
<p>_: 서로 독립적인 명령어들을 최대한 찾고, 이들을 오버랩해서 실행
_</p>
<h2 id="ෆ-ilp-예시">ෆ ILP 예시</h2>
<p>$$
a = x<em>x + y</em>y + z*z
$$
<img src="https://velog.velcdn.com/images/ii_seo/post/96643f1b-b25a-4192-90e3-08ae2e3b10ff/image.png" alt=""></p>
<ul>
<li>processor : a machine that executes the assembly instructions in sequence<pre><code>               → 컴퓨터 운영을 위해 기본적인 명령어들을 처리하고 반응하기 위한 논리회로</code></pre></li>
<li>PC (Program Counter) : references the instruction to be executed</li>
</ul>
<p>→ 해당 예시에서 모든 instruction을 실행하려면 5 cycle이 걸림</p>
<p><img src="https://velog.velcdn.com/images/ii_seo/post/88a3f5fc-4e2d-4e0d-bb91-409f2e13dd2b/image.png" alt=""></p>
<ul>
<li>하지만 위와 같이 여러 개의 execution unit이 있다면 여러 개의 instruction을 한 cycle에 수행 가능</li>
<li>그럼에도 1 cycle에 모든 instruction을 수행할 수는 없음<ul>
<li>(4) instruction의 경우, (1)과 (2)의 수행결과가 있어야 수행할 수 있으므로 ..</li>
<li>⇒ <strong>Data Dependency</strong>
   : 한 작업이나 명령이 다른 작업의 결과에 의존할 때 발생하는 상황</li>
</ul>
</li>
<li>3 cycle에 모든 instruction 수행 완료<ul>
<li>Q: execution unit은 몇 개가 필요한가 ..<ul>
<li>기본적으로 3개 : (1), (2), (3) 의 연산을 한 번에 수행</li>
<li>2개도 가능 : (3), (4) 의 연산은 독립적이기 때문에 한 번에 수행해도 ok !</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="ෆ-scalar-processor-vs-superscalar-processor">ෆ Scalar processor VS Superscalar processor</h2>
<ul>
<li><p>Scalar processor : 한 번에 하나의 데이터를 처리하는 CPU (1ALU)</p>
<ul>
<li>한 번에 하나의 instruction만 처리 가능</li>
<li>각 instruction는 순차적으로 처리 (다음 instruction은 현재의 instruction의 처리가 완료된 후 실행)</li>
<li>단순하고 예측하기 쉬운 성능 제공</li>
<li>처리 능력이 제한적
<img src="https://velog.velcdn.com/images/ii_seo/post/639e116e-a053-4fe0-9ef6-41dbd70ca3ae/image.png" alt=""></li>
</ul>
</li>
<li><p>Superscalar processor</p>
<ul>
<li>한 번에 여러 개의 instruction 처리 가능</li>
<li>여러 ALU를 통해 동시에 다양한 instruction 실행</li>
<li>instruction을 parallel(병렬적)으로 처리
<img src="https://velog.velcdn.com/images/ii_seo/post/f52c0f30-f02f-4ee6-9c8f-631294db8694/image.png" alt=""></li>
</ul>
</li>
</ul>
<h3 id="⇒-주요-차이점">⇒ 주요 차이점</h3>
<ul>
<li>처리 능력 : Superscalar processor는 Scalar processor보다 더 많은 instruction을 동시에 처리 가능 → 처리 능력 ↑</li>
<li>구조 복잡성 : Superscalar processor는 여러 ALU를 동시에 관리하고 조율해야 하므로 Scalar processor보다 더 복잡한 구조</li>
<li>성능 측면에서의 효율성 : Superscalar processor는 parallel 처리를 통해 더 빠른 성능을 제공<pre><code>                                but, 이로 인해 설계와 구현이 더 어려워짐, 비용도 ↑</code></pre></li>
</ul>
<p><img src="https://velog.velcdn.com/images/ii_seo/post/8828ab95-75a5-4c2e-a609-0a19c9a12d19/image.png" alt=""></p>
<h2 id="ෆ-superscalar-processor">ෆ Superscalar processor</h2>
<p>컴퓨터 성능 → CPU의 기본 처리 속도 = cycle 당 instruction을 한 번 처리하는 것 (IPC = 1)</p>
<p>⇒ 한 cycle에 instruction이 하나 이상 처리되면 CPU는 그만큼 한 번에 여러 개의 연산 처리가 가능 ~ 체감 속도 ↑</p>
<p>ex. “ $a = (x+x)*y$ “</p>
<p>해당 연산 결과를 뽑으라는 instruction을 줬다고 가정했을 때</p>
<ul>
<li>IPC = 1인 환경 → 1 cycle이 지나면 곱셈의 결과를 얻을 수 있음</li>
<li>하지만 해당 식은 덧셈의 결과를 바탕으로 곱셈의 결과를 얻어야 함 → 1 cycle 이상 걸림<ul>
<li>덧셈의 instruction을 수행하는 데에 1 cycle이 소비되기 때문</li>
</ul>
</li>
<li>컴퓨터 성능 향상 불가 ..</li>
</ul>
<h3 id="⇒-instruction-간의-dependence-때문">⇒ instruction 간의 dependence 때문</h3>
<h3 id="이를-극복하기-위한-방법-→-superscalar-processor">이를 극복하기 위한 방법 → Superscalar processor</h3>
<p>instruction이 처리되는 path를 여러 개 만들고 각각의 instruction을 해당 path를 통해서 처리하게 하면 됨</p>
<ul>
<li><p>지금까지 생각한 MIPS 구조의 pipeline을 그대로 따라가면서 그것과 비슷한 line을 하나 더 생성</p>
<ul>
<li><p>하나는 data만 처리할 수 있게</p>
</li>
<li><p>하나는 instruction만 처리할 수 있게</p>
<p>⇒ instruction을 읽어오면서 data pipeline을 통해서 결과를 뽑을 수 있게끔</p>
</li>
</ul>
</li>
<li><p>이 모든 작업을 parallel하게 처리</p>
<ul>
<li>IPC = 1인 형태를 넘어서서 성능향상 구현 가능</li>
</ul>
</li>
</ul>
<p>independent한 instruction을 어떻게 찾냐 → dynamic scheduling을 통해서 ..</p>
<h2 id="ෆ-superscalar-processor가-그럼-limit-없이-성능이-향상되는가">ෆ Superscalar processor가 그럼 limit 없이 성능이 향상되는가</h2>
<ul>
<li><p>Superscalar processor의 장점</p>
<ol>
<li><p>프로그램 호환성</p>
<p> : 기존의 프로그램 코드를 변경하지 않고도 성능 향상 가능</p>
<p> processor가 자동으로 instruction 간의 independency 판별, parellel로 실행 가능한 instruction 선정</p>
</li>
<li><p>효율성</p>
<p>: 복수의 ALU를 통해 instruction을 동시에 처리 → 처리 속도가 ↑</p>
<p>(특히 복잡한 계산이나 대량의 data 처리가 필요한 application에서 유용)</p>
</li>
</ol>
</li>
</ul>
<pre><code>### ⇒ 한계 ?
![](https://velog.velcdn.com/images/ii_seo/post/0d918521-1366-4afe-bedd-2851cae84048/image.png)
  1. 자원의 한계

      : 동시에 실행할 수 있는 instruction의 수는 결국 processor 내의 ALU의 수와 관련 자원에 의해 제한됨

  2. instruction dependency

      : 일부 instruction은 다른 instruction의 result에 의존 → 성능의 병목 현상

  3. 메모리 대역폭

      : processor가 더 많은 processor를 동시에 처리하려면, memory로부터 data를 더 빠르게 가져와야 함</code></pre><h2 id="ෆ-processor-scaling-trend">ෆ Processor scaling trend</h2>
<p><img src="https://velog.velcdn.com/images/ii_seo/post/27a1a5c0-a768-4c74-99ee-6f425e7d749e/image.png" alt=""></p>
<ul>
<li>무어의 법칙 : 집적회로 내의 transistor 수가 약 18개월에서 24개월마다 2배로 증가한다는 관찰을 기반으로 함<pre><code>               → 초기 몇십년 동안 processor 성능 향상의 주요 동력이었으나, 물리적 한계와 제조 비용의 증가로 인해 지속 가능한지 …</code></pre></li>
<li>clock speed : processor가 초 당 수행할 수 있는 cycle 수<pre><code>                  이 clock speed를 늘리는 것에 집중했으나, 높은 clock speed는 열 문제와 전력 소비 증가를 초래</code></pre></li>
<li>power : 효율적인 에너지 설계가 중요 .. (배터리 수명 .. 등) → 전력 소비를 줄이면서도 성능을 최적화 하는 방향으로 ..</li>
</ul>
<h3 id="⇒-multi-core--multi-threading">⇒ multi-core , multi-threading</h3>
<p>: clock speed 향상의 한계에 직면 (발열 문제)</p>
<p>→ 여러 개의 처리 core를 하나의 칩에 집적하는 <strong>multi-core processor</strong> !</p>
<ul>
<li>단일 processor 내에서 parellel 처리가 가능해져 성능이 향상</li>
<li>hyper-threading 같은 기술을 통해 core 당 여러 thread를 동시에 처리할 수 있음</li>
</ul>
<h1 id="❦-multi-core-processors-mimd-multiple-instruction-multiple-data">❦ Multi-core processors (MIMD, Multiple Instruction Multiple Data)</h1>
<h2 id="ෆ-multi-core-processor">ෆ Multi-core processor</h2>
<p><img src="https://velog.velcdn.com/images/ii_seo/post/09e14120-c404-4409-aedf-8ded21ac822d/image.png" alt=""></p>
<ul>
<li>여러 개의 작업을 보다 효율적으로 처리하기 위해 2개 이상의 느린 processor가 붙어있는 ‘집적회로’</li>
<li>power가 증가되고 열 손실이 감소한 2개의 processing 엔진 &gt; processing core가 하나일 자원이 부족한 칩</li>
</ul>
<p>  e.g. 80% clock frequency → 2 cores : $$2*0.8 = 1.6 &gt; 1$$</p>
<p><img src="https://velog.velcdn.com/images/ii_seo/post/48909362-97c9-4bdf-bb02-66cf05f09d3f/image.png" alt="">
다시 돌아와서 .. multi-core processor에서 다음 코드를 돌린다고 가정하면,</p>
<h3 id="오히려-드는-시간이-늘어나게-됨-">오히려 드는 시간이 늘어나게 됨 ..</h3>
<p>→ multi-thread와 같은 기법을 사용하지 않으면, core 한개가 노는 상태 ..</p>
<p>~ 결국 위 코드의 실행 시간은 $1 * 0.8 = 0.8 &lt; 1$</p>
<h3 id="⇒-thread를-이용해야-">⇒ thread를 이용해야 ..!</h3>
<h2 id="ෆ-thread">ෆ Thread</h2>
<p>: process 내에서 process의 자원을 이용하여 실행되는 여러 흐름의 단위</p>
<p>: process 내에서 실제로 작업을 수행하는 주체</p>
<p>→ 운영체제의 스케줄러에 의해 독립적으로 관리될 수 있는 프로그래밍된 명령어의 가장 작은 시퀀스
<img src="https://velog.velcdn.com/images/ii_seo/post/22ef82e9-8969-4f05-8df2-bbce0ae86683/image.png" alt=""></p>
<ol>
<li>subroutine을 호출하는 경우<ul>
<li>subroutine : 프로그램 내에서 다른 부분에서 실행되는 코드 블록</li>
<li>현재 실행 중인 작업을 중단하고 해당 서브루틴으로 제어를 이동</li>
<li>서브루틴을 실행한 후 다시 호출한 지점으로 돌아와 작업을 계속할 수 있음</li>
<li>일반적인 함수 호출 방식과 유사한 방식</li>
</ul>
</li>
<li>새로운 thread를 시작하는 경우<ul>
<li>새로운 스레드가 현재 스레드와 병렬로 실행
→ 여러 작업을 동시에 처리 가능</li>
<li>새로운 스레드를 시작하는 작업은 일반적으로 다른 함수나 메소드 내에서 수행
→ 새로운 스레드의 실행을 시작하고, 해당 스레드가 실행될 함수 또는 코드 블록을 지정</li>
</ul>
</li>
</ol>
<h2 id="ෆ-process-vs-thread">ෆ Process VS Thread</h2>
<ul>
<li><p>process<img src="https://velog.velcdn.com/images/ii_seo/post/6d5da39e-4129-4ce2-8b04-f7c0b42772cf/image.png" alt=""></p>
<blockquote>
<p>작업 중인 프로그램</p>
</blockquote>
<ul>
<li>process가 memory에 올라갈 때 OS로부터 시스템 자원을 할당받음<ul>
<li>process마다 각각 독립된 memory 영역<ul>
<li><strong>기본적으로 process끼리 다른 process의 memory에 직접 접근 불가</strong></li>
</ul>
</li>
<li>Code/Data/Stack/Heap의 형식</li>
</ul>
</li>
</ul>
<p>→ 한 프로세스를 실행하다가 오류가 발생해서 프로세스가 강제로 종료된다면, 다른 프로세스에게 어떤 영향이 있을까? 
: 공유하고 있는 파일을 손상시키는 경우가 아니라면 아무런 영향을 주지 않는다.</p>
</li>
<li><p>thread</p>
<blockquote>
<p>프로세스의 코드에 정의된 절차에 따라 실행되는 특정한 수행 경로</p>
</blockquote>
<p>  <img src="https://velog.velcdn.com/images/ii_seo/post/6ae76203-7bc9-423b-8555-e7e3d11c2594/image.jpeg" alt=""></p>
<ul>
<li><p>메모리를 서로 공유할 수 있음</p>
<ul>
<li><p>Code/Data/Heap 형식으로 할당된 메모리 영역을 공유</p>
<ul>
<li><p>heap 메모리는 공유하기 때문에 서로 다른 스레드에서 가져와 읽고 쓸 수 있음</p>
<p>→ 어떤 스레드 하나에서 오류가 발생한다면 같은 프로세스 내의 다른 스레드 모두가 강제로 종료</p>
</li>
</ul>
</li>
<li><p>address space</p>
</li>
<li><p>…</p>
</li>
</ul>
</li>
<li><p>공유되지 않는 resource</p>
<ul>
<li>Stack 형식으로 할당된 메모리 영역은 따로 할당<ul>
<li>stack: 함수 호출 시 전달되는 인자, 되돌아갈 주소값, 함수 내에서 선언하는 변수 등을 저장하는 메모리 공간</li>
<li>독립적인 스택을 가졌다 → 독립적인 함수 호출이 가능하다 → 독립적인 실행 흐름이 추가된다</li>
<li>독립적인 stack을 가짐으로써 thread는 독립적인 실행 흐름을 가질 수 있게 됨</li>
</ul>
</li>
<li>PC<ul>
<li>각 스레드는 자체 프로그램 카운터를 가지고 다음에 실행할 명령어의 위치를 추적</li>
<li>instruction이 같다면 PC 공유도 가능 .. 하긴 함</li>
</ul>
</li>
<li>register<ul>
<li>각 스레드는 자체 레지스터를 가지고 데이터 및 연산을 처리</li>
<li>instruction이 같아도 register는 공유 불가 → thread마다 다른 data를 다루기 때문 ,,</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<h2 id="ෆ-example-code">ෆ Example code</h2>
<pre><code>#include &lt;iostream&gt;
const int N = 100;
int main()
{
    int a[N], b[N], k[N], c[N];
    for(int i=0;i&lt;N;i++) {
        a[i] = i;
        b[i] = 1000*i;
        k[i] = 10;
        c[i] = 0;
    }
    for(int i=0;i&lt;N;i++) {
        c[i] = k[i]*a[i];
        c[i] += k[i]*b[i];
    }
    return 0;
}</code></pre><h3 id="multi-thread-program">multi-thread program</h3>
<pre><code>#include &lt;iostream&gt;
#include &lt;thread&gt;
#include &lt;vector&gt;

// 정수 포인터를 선언하여 동적으로 할당된 배열을 가리키는 역할
int *a, *b, *k, *c;

// 벡터 연산을 수행하는 함수
// mac : multiply-accumulate, 주어진 두 배열을 곱하고 결과를 누적하는 역할
// tid : thread의 index, num_threads : 전체 thread의 수
void mac(int tid, int num_threads)
{
    // 전체 vector의 크기를 전체 thread 수로 나누어 각 thread가 처리할 부분을 결정
    for(int i=0;i&lt;N/num_threads;i++)
    {
        // idx : 현재 thread가 처리할 index
        int idx = tid*(N/num_threads) + i;
        c[idx] = k[idx] * a[idx];
        c[idx] += k[idx] * b[idx];
    }
    return;
    }
int main(int argc, char* argv[])
{
...
    // thread를 저장할 vector를 선언
    std::vector&lt;std::thread&gt; threads;
    // 전체 thread 수만큼 반복하면서 thread를 생성
    for(int t=0;t&lt;NT;t++) {
        // 새로운 스레드를 생성하고 벡터 threads에 추가
        // 각 스레드는 mac 함수를 호출하며, 스레드의 인덱스(t)와 전체 스레드 수(NT)를 전달
        threads.push_back(std::thread(mac, t, NT));
    }
    // 모든 스레드가 종료될 때까지 대기
    for(auto&amp; thread: threads) {
        thread.join();
    }
    return 0;
}</code></pre><p><img src="https://velog.velcdn.com/images/ii_seo/post/d6353d8f-342d-48f4-a955-48bf8458d912/image.png" alt=""></p>
<h3 id="data-parallel-expressions">Data-Parallel expressions</h3>
<p>병렬화를 위한 loop나 반복문을 쉽게 target으로 지정할 수 있는 개념</p>
<p>→ 프로그래머가 루프의 반복이 독립적이라고 선언하고, 해당 루프를 병렬로 실행할 수 있도록 지시</p>
<pre><code>#include &lt;iostream&gt;

//const int N = 1000000;
#defiine N 1000000000LL
int main()
{
...

#pragma omp parallel for
    for(long long int i=0;i&lt;N;i++) {
        c[i] = k[i]*a[i];
        c[i] += k[i]*b[i];
    }

    return 0;
}</code></pre><ul>
<li><p>각 반복이 다른 반복과 독립적으로 실행될 수 있는 경우
: 독립성을 활용하여 병렬 처리를 수행</p>
</li>
<li><p>이러한 독립적인 반복을 탐지하고, 해당 반복을 여러 스레드로 병렬화하여 동시에 실행할 수 있는 코드를 생성</p>
</li>
<li><p>OpenMP : data-parallel expression을 사용하여 loop를 병렬화</p>
<ul>
<li><p>프로그래머는 OpenMP의 지시문을 사용하여 루프가 독립적임을 선언</p>
</li>
<li><p>OpenMP는 해당 지시문을 해석하여 루프를 병렬로 실행하는 코드를 생성</p>
<p>→ 프로그래머는 직접 thread를 관리하지 않고도 병렬 처리를 수행할 수 있음</p>
</li>
</ul>
</li>
</ul>
<h1 id="❦-vector-processing-simd-single-instruction-multiple-data">❦ Vector processing (SIMD, Single Instruction Multiple Data)</h1>
<h2 id="ෆ-multicore-processor의-fetchdecode-부분이-여러-개여야-하는가-">ෆ Multicore processor의 Fetch/Decode 부분이 여러 개여야 하는가 ,,</h2>
<p>이전의 예시에서
<img src="https://velog.velcdn.com/images/ii_seo/post/a487456b-edb4-4af7-b424-d0549a761b13/image.png" alt=""></p>
<ul>
<li><p>위의 코드가 정확히 같은 code, data만 다른 data를 다루는 것</p>
<h3 id="→-fetchdecode가-꼭-2개의-unit일-필요가-없음--vector-processing-">→ fetch/decode가 꼭 2개의 unit일 필요가 없음 : Vector processing !</h3>
</li>
<li><p>execution context는 2개의 unit 이어야 함</p>
<ul>
<li>thread별로 각각 다른 memory가 필요하기 때문 ..</li>
</ul>
</li>
</ul>
<h2 id="ෆ-vector-processing">ෆ Vector processing</h2>
<ul>
<li>하나의 instruction을 통해 여러 개의 data 요소를 동시에 처리하는 기술</li>
</ul>
<h3 id="하나의-명령어가-여러-개의-데이터-요소에-대해-동시에-작업을-수행할-수-있도록">하나의 명령어가 여러 개의 데이터 요소에 대해 동시에 작업을 수행할 수 있도록</h3>
<p>→ 하나의 명령어가 다수의 연산장치(ALU)에서 동시에 실행되도록 : 병렬성 ↑, 연산량 효율적으로 처리</p>
<pre><code>#include &lt;immintrin.h&gt;

#pragma omp parallel for
for(long long int i=0;i&lt;N;i+=8) {

        // __m256i : 256비트(32바이트)크기의 정수형 벡터를 나타내는 데이터 타입
        // _mm256_load_si256 : AVX register에 data load
        __m256i A = _mm256_load_si256((__m256i*)(&amp;a[i]));
        __m256i B = _mm256_load_si256((__m256i*)(&amp;b[i]));
        __m256i K = _mm256_load_si256((__m256i*)(&amp;k[i]));
        // _mm256_mullo_epi32 : 두 개의 256비트 정수형 벡터의 각 요소를 곱한 결과를 반환
        __m256i C1 = _mm256_mullo_epi32(A,K);
        __m256i C2 = _mm256_mullo_epi32(B,K);
        // _mm256_add_epi32 : 두 개의 256비트 정수형 벡터의 각 요소를 더한 결과를 반환
        __m256i C = _mm256_add_epi32(C1,C2);

        // _mm256_store_si256 : AVX register의 데이터를 메모리에 저장
        _mm256_store_si256((__m256i*)(&amp;c[i]), C);
    }
    return 0;
}</code></pre><p><img src="https://velog.velcdn.com/images/ii_seo/post/537de68e-53cd-4d56-98e9-6f437971b997/image.png" alt=""></p>
<ul>
<li><p>SSE(Streaming SIMD Extensions)</p>
<ul>
<li>128비트 SIMD 명령어 세트 : 4<em>32비트, 2</em>64비트</li>
<li>이전의 기본 x86 명령어 세트를 확장하여 추가된 기능</li>
</ul>
</li>
<li><p>AVX2(Advanced Vector Extentions 2)</p>
<ul>
<li>256비트 SIMD 명령어 세트 : 8<em>32비트, 4</em>64비트</li>
<li>SSE의 확장으로, 더 많은 데이터를 한 번에 처리할 수 있도록 하여 성능을 향상</li>
</ul>
</li>
<li><p>AVX-512</p>
<ul>
<li>512비트 SIMD 명령어 세트 : 16<em>32, 8</em>64</li>
<li>AVX2의 확장으로, 더 많은 데이터를 한 번에 처리할 수 있어서 더 높은 성능을 제공</li>
</ul>
</li>
<li><p>컴파일러는 코드를 분석하여 병렬성을 추론하고 필요한 경우에는 자동으로 SIMD 명령어를 생성</p>
</li>
<li><p>프로그래머가 직접 SIMD 명령어를 사용하고 싶은 경우→ ntrinsics(인트린식)라는 특별한 함수를 사용 (위의 코드 예시 참고)</p>
<p>  → 성능을 더욱 향상시킬 수 있음</p>
</li>
</ul>
<h3 id="conditional-execution-">Conditional execution ?</h3>
<p>벡터 처리에서 조건부 실행을 구현하는 방법</p>
<p>⇒ <strong>Predication : Masking</strong><img src="https://velog.velcdn.com/images/ii_seo/post/10a269bb-2759-4b0f-b8e8-8fa7abb2c4d4/image.png" alt=""></p>
<ul>
<li><p>SIMD instruction을 사용하여 조건을 확인</p>
</li>
<li><p>해당 조건이 참(True)일때만 연산을 수행</p>
<ul>
<li>해당 연산에 대한 마스크 설정 → 해당 연산이 실행되도록 ,,</li>
</ul>
</li>
<li><p>해당 조건이 거짓인 경우 연산을 건너뜀</p>
<ul>
<li>해당 연산에 대해 마스크를 설정하지 않음 → 해당 연산이 무시되도록 ,,</li>
</ul>
</li>
<li><p>전체적인 연산량에 대한 이점을 제공
but! 일부 연산은 실제로는 실행되지 않으므로 이로 인해 하드웨어의 이용률이 낮아질 수 있음
  ⇒ 마스킹을 사용하면 일부 ALU(산술 논리 장치)가 실제로는 유휴 상태일 수 있다 ,,</p>
</li>
<li><p>GPU(Graphic Processing Unit)에서도 마스킹 기술이 널리 사용됨</p>
</li>
</ul>
<h1 id="❦-정리">❦ 정리</h1>
<h2 id="ෆ-sisd--single-instruction-single-data">ෆ SISD : Single Instruction, Single Data</h2>
<p><img src="https://velog.velcdn.com/images/ii_seo/post/f6f0b7d5-263d-4f8c-ad25-bacbfc9ac150/image.png" alt="">
<img src="https://velog.velcdn.com/images/ii_seo/post/274f33f7-1341-4abf-b2df-f056441240de/image.png" alt=""></p>
<ul>
<li><p>Scalar processor</p>
</li>
<li><p>Superscalar processor</p>
<h2 id="ෆ-mimd--multiple-instruction-multiple-data">ෆ MIMD : Multiple Instruction, Multiple Data</h2>
<p><img src="https://velog.velcdn.com/images/ii_seo/post/ce6f6202-88e3-4415-a6fe-7c31233641d0/image.png" alt="">
<img src="https://velog.velcdn.com/images/ii_seo/post/2bee2da6-b09a-4bf1-959a-5f08a7c21d19/image.png" alt=""></p>
</li>
<li><p>Multi-core processor</p>
<h2 id="ෆ-simd--single-instruction-multiple-data">ෆ SIMD : Single Instruction, Multiple Data</h2>
<p><img src="https://velog.velcdn.com/images/ii_seo/post/bbb6c8c3-530e-4cd5-ad91-37a84ccf14d0/image.png" alt="">
<img src="https://velog.velcdn.com/images/ii_seo/post/26751a6a-ae25-4deb-8c4e-09294b562ada/image.png" alt=""></p>
</li>
<li><p>Vector processor</p>
<h2 id="ෆ-misd--multiple-instruction-single-data">ෆ MISD : Multiple Instruction, Single Data</h2>
<p>일반적으로 병렬성을 향상시키는 데 사용되지 않음
<img src="https://velog.velcdn.com/images/ii_seo/post/ae68db98-faaa-4155-ab5e-9ba81f06132b/image.png" alt="">
<img src="https://velog.velcdn.com/images/ii_seo/post/984f925e-777e-4945-b3b1-5696ce6dde28/image.png" alt="">
→ 특정 유형의 작업에 최적화된 특수 목적 프로세서에서 사용</p>
</li>
<li><p>Systolic Array</p>
<ul>
<li>여러 개의 프로세서 코어가 특정한 데이터 스트림을 따라 일련의 연산을 수행하는 구조</li>
<li>주로 행렬 곱셈과 같은 수치 연산에서 사용</li>
</ul>
</li>
<li><p>Google TPU</p>
<ul>
<li>대규모 딥러닝 모델의 추론 및 학습을 가속화하기 위해 설계</li>
</ul>
</li>
<li><p>그리고 많은 다른 NPU(Neural Processing Unit)</p>
<ul>
<li>NPU : 인공 신경망을 실행하고 가속화하기 위해 설계된 전용 하드웨어 장치</li>
</ul>
</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>