<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>LE LABO</title>
    <link>https://laboratorium.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Fri, 10 Jul 2026 01:57:39 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>커피살인마</managingEditor>
    <item>
      <title>Synchronization</title>
      <link>https://laboratorium.tistory.com/39</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Race Condition 경쟁 상태&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 프로그램에서 Race condition (경쟁 상태)이 흔히 발생하는데 이는 여러 개의 스레드가 하나의 공유 메모리를 동시에 읽고 쓸 때 발생한다. 덧셈과 뺄셈같이 한 번에 이루어지는 계산은 아토믹 함수를 이용하고 더 넓은 범위의 코드는 크리티컬 섹션을 사용하여 경쟁 상태를 해결한다. 이 두 방법은 손쉬운 방법이지만 불필요하게 많이 사용하게 되면 오히려 프로그램의 효율이 떨어진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1670830188210&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//아래 코드 실행 시 10000*512개의 스레드가 실행되지만 Race condition(경쟁 상태) 발생으로 
//결과로 더 적은 수가 출력됨.
__global__ void ThreadCountDataRace(int* nThreadCount) {
	(*nThreadCount)++;
}

int main() {
	const int nBlocks = 10000; 
	const int nThreads = 512;

	int host_nThreadCount = 0;

	int* dev_nThreadCount;

	cudaMalloc((void**)&amp;amp;dev_nThreadCount, sizeof(int));

	cudaMemset(dev_nThreadCount, 0, sizeof(int));

	ThreadCountDataRace &amp;lt;&amp;lt; &amp;lt;nBlocks, nThreads &amp;gt;&amp;gt; &amp;gt; (dev_nThreadCount);

	cudaMemcpy(&amp;amp;host_nThreadCount, dev_nThreadCount, sizeof(int),cudaMemcpyDeviceToHost);

	printf(&quot;실행 개수 : %d 개 \n&quot;, host_nThreadCount);

	cudaFree(dev_nThreadCount);

	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Atomic Functions 아토믹 함수&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 주소가 가리키는 값을 읽고 나서 연산을 수행한다. 같은 어드레스 결과를 저장하고 연산 완료 후 값을 반환한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Thread Synchronization 스레드 동기화 : syncthreads&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록 내의 스레드를 동기화하도록 __syncthreads()함수 사용. 하나의 멀티프로세서에서 블록 단위로 스레드가 동작하기 때문에 __syncthreads()함수의 유효범위는 블록 내 스레드에 한정된다. 코드 내에 __syncthreads() 함수가 있으면 블록 내의 모든 스레드가 이 부분에 도달할 때 까지 대기한다.&amp;nbsp; __syncthreads()함수는 블록 내 스레드 동기화가 필요한 지점에 삽입하여 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Global Synchronizaton 글로벌 동기화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 동기화는 블록 단위의 동기화를 유지하기 위한 것으로, 글로벌 동기화를 지원하는 API가 따로 있는 것은 아니다. 글로벌 동기화를 유지하려면 커널 함수를 분리하여 호출하는 방법을 사용하며 커널을 반복 호출해야 하므로 커널 호출을 위한 부하가 추가된다. 글로벌 동기화를 위한 구현은 하나의 커다란 커널을 동기화가 필요한 지점별로 커널을 작게 나누어 여러 번 호출하거나 커널의 반복 호출을 하는 방법으로 이루어진다. 작은 커널로 나누어 구현할 때 의존성이 있는 데이터 간의 순서를 잘 지켜야 한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1670833398648&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//글로벌 동기화의 구현 방법
//1.동기화를 위한 커널의 분할
kernel_step1&amp;lt;&amp;lt;&amp;lt;nblocks, nThreads&amp;gt;&amp;gt;&amp;gt;(a,b); //a를 연산하여 b의 결과를 얻음.
kernel_step2&amp;lt;&amp;lt;&amp;lt;nblocks, nThreads&amp;gt;&amp;gt;&amp;gt;(b,c); //b를 연산하여 c의 결과를 얻음.

//2.커널의 반복 호출
for ( int i=0; i&amp;lt; limit, i++) 
{
	kernel&amp;lt;&amp;lt;&amp;lt;nblocks, nThreads&amp;gt;&amp;gt;&amp;gt;(input,result);
    input = result; 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CUDA</category>
      <author>커피살인마</author>
      <guid isPermaLink="true">https://laboratorium.tistory.com/39</guid>
      <comments>https://laboratorium.tistory.com/39#entry39comment</comments>
      <pubDate>Mon, 12 Dec 2022 17:23:52 +0900</pubDate>
    </item>
    <item>
      <title>Stream</title>
      <link>https://laboratorium.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CUDA 프로그램의 구조&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 호스트에서 디바이스로 입력 데이터 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 커널 함수에서 데이터 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 결과 데이터를 디바이스에서 호스트로 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 과정을 차례대로 처리하면 입력 데이터가 전송되는 시간동안 GPU 프로세서는 대기하게 된다. 만일 입력 데이터를 좀 더 작게 나누고 입력 데이터 전송이 완료된 것부터 GPU에서 계산을 하는 동시에 데이터를 전송하면 프로그램의 전체 효율이 좋아진다. 데이터 입력을 받으면서 GPU 프로세서가 함께 계산하며 동시성을 높여 주는 것이 CUDA의 스트림이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 처리 과정을 여러 개로 나누어 데이터 입력 과정과 동시에 처리할 수 있도록 하려면 데이터의 의존성(Dependency)이 없어야 한다. 앞서 입력된 데이터의 결과가 뒤에 입력될 데이터에 사용되지 않아야 독립적으로 동시에 처리할 수 있다. CUDA 스트림은 의존 관계가 있는 데이터 처리에서도 실행 순서를 지킬 수 있는 기능도 제공하여 편리하게 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;병행 복사&lt;/b&gt; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 복사와 실행을 병렬 처리하려면 복사하는 데이터의 크기를 작게 나누고, 비동기 복사 함수를 호출해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1670551527310&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Asynchronous copy 
cudaError_t cudaMemcpyAsync( void * dst, const void * src, size_t count, enum cudaMemcpyKind kind, cudaStream_t stream = 0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cudaMemcpyAsync()를 사용하는 방법은 cudaMemcpy()와 비슷하지만 4번째 인자로 스트림 객체를 넣을 수 있고 전송 시 사용하는 호스트 메모리는 cudaMallocHost()함수를 이용하여 생성한 고정 메모리를 사용해야 한다. 작게 나눈 데이터를 디바이스에 복사하고서 커널 함수가 실행되어 해당하는 데이터를 계산한다. 복사가 완료되기 전에 커널 함수가 실행되면 프로그램의 오류가 발생하게 되고 복사가 완료되고 나서도 커널 함수가 실행되지 않으면 병행 실행의 효과가 떨어진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 순서를 맞추는 기능을 하는 것이 스트림 객체이다. 스트림 객체는 cudaStreamCreate()함수로 생성하고 cudaStreamDestroy()함수로 소멸시킨다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1670551854596&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Stream 객체 생성 및 소멸 
cudaError_t cudaStreamCreate( cudaStream_t *pStream);
cudaError_t cudaStreamDestroy( cudaStream_t stream);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1670574008396&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//스트림 객체 생성, 소멸
cudaStreamCreate(&amp;amp;stream1);
cudaStreamCreate(&amp;amp;stream2);

cudaMemcpyAsync(deviceInput1, hostInput1, dataSize1, cudaMemcpyHostToDevice, stream1);
cudaMemcpyAsync(deviceInput2, hostInput2, dataSize2, cudaMemcpyHostToDevice, stream2);

kernel&amp;lt;&amp;lt;&amp;lt;nBlocks, nThreads, 0, stream1&amp;gt;&amp;gt;&amp;gt;(deviceInput1, deviceOutput1);
kernel&amp;lt;&amp;lt;&amp;lt;nBlocks, nThreads, 0, stream2&amp;gt;&amp;gt;&amp;gt;(deviceInput2, deviceOutput2);

cudaStreamDestroy(stream1);
cudaStreamDestroy(stream2);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CUDA</category>
      <author>커피살인마</author>
      <guid isPermaLink="true">https://laboratorium.tistory.com/38</guid>
      <comments>https://laboratorium.tistory.com/38#entry38comment</comments>
      <pubDate>Fri, 9 Dec 2022 17:36:11 +0900</pubDate>
    </item>
    <item>
      <title>성능 측정</title>
      <link>https://laboratorium.tistory.com/37</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램의 성능을 측정하기 위해 소요 시간을 계산하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 소요시간 측정&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 타이머 활용 (NVCC(CUDA compiler)가 Windows API를 간단하게 사용할 수 있는 환경이 아니므로 비추천)&lt;/li&gt;
&lt;li&gt;GPU 타이머 활용&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1670422990181&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Measure the execution time of a program, unit : msec
#include &amp;lt;cutil.h&amp;gt; //CUDA utility tools
unsigned int timer;
cutCreateTimer(&amp;amp;timer);    //Create a timer
cutStartTimer(timer);      //Start 
...
cutStopTimer(timer);       //stop
double elapsed_time = cutGetTimerValue(timer);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA 타이머 사용 시 호스트에서 커널 함수를 호출하면 비동기로 바로 전환되어 정확한 시간을 측정할 수 없으므로 커널 함수가 완료될 때까지 기다리기 위해서 cudaThreadSynchronize()함수를 추가하여 시간을 측정한다. 비동기 함수가 아닌 함수의 실행시간을 측정할 때에는 동기화 없이 바로 시간을 측정하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트 API의 활용&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Device query&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA SDK 샘플 예제의 Device Query 프로젝트를 실행하여 GPU의 사양과 CUDA SDK의 버전 확인 가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. Cubin File&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA 프로그램 컴파일 시 생성되는 .cubin 파일을 보면 레지스터의 사용량과 공유 메모리의 사용량 같은 정보를 얻을 수 있다. NVCC 컴파일러에 -cubin 옵션을 추가하면 '.cubin' 파일이 생성된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lmem - local memory 사용량 [byte]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;smem - shared memory 사용량 [byte]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;reg - 하나의 스레드에서 사용하는 레지스터 양 [개수]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;액티브 블록&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 함수에서 사용하는 레지스터와 공유 메모리의 사용량에 따라 SM(스트리밍 멀티 프로세서)이 관린하는 활성화된 블록이 많아진다. 동시에 관리하는 블록이 많다는 것은 GPU의 가동 효율이 좋다는 것을 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cubin파일을 통해서 개별 스레드가 사용하는 레지스터의 수와 블록에서 사용하는 공유 메모리를 알 수 있다. 이 정보를 이용하여 GPU에서 동시에 관리되는 스레드와 워프, 블록의 개수를 알 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;레지스터 사용에 따른 활성화 블록, 워프 계산&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1개의 스레드가 16개의 레지스터를 사용하고, 하나의 블록이 256개의 스레드로 구성된 프로그램에서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SM의 레지스터가 8192개 일 때 최대 8192/16 = 512개의 스레드를 구성할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1개의 블록은 256개의 스레드로 구성되었기 때문에 활성화 할 수 있는 블록의 수는 512/256&amp;nbsp; = 2개이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1개의 워프는 32개의 스레드로 구성되어 있기 때문에 활성화 워프의 수는 512/32 = 16개이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;공유 메모리 사용에 따른 활성화 블록 계산&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1개의 블록이 2KB의 공유 메모리를 사용하는 경우에서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SM의 공유 메모리 크기가 16KB일 때 블록은 16KB/2KB = 8개 활성화할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 위의 조건이 이어지면 사용할 수 있는 자원의 제한으로 활성화 할 수 있는 제한 조건은&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Min(위의 레지스터에 의한 활성화 블록 수, 공유 메모리에 의한 활성화 블록 수) = 레지스터에 의한 활성화 블록 수 2개이다. CUDA SDK Toolkit은 이런 계산을 간단하게 지원해주는 CUDA GPU Occupancy Calculator를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. CUDA GPU Occupancy Calculator&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA 점유 계산기는 블록의 스레드 개수, 스레드의 레지스터 사용량, 블록의 공유 메모리 사용량을 입력하면 활성화 블록의 수를 자동으로 계산해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. Visual Profiler&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA SDK Toolkit에 포함된 Visual profiler를 사용하면 구현한 프로그램의 성능을 측정할 수 있다. VIsual Profiler는 CUDA프로그램에서 글로벌 메모리의 액세스 성능과 스레드의 동작 상태, 모듈별 소요 시간 등의 정보를 제공하기 때문에 이를 가지고 튜닝해야 할 위치와 방법에 대한 단서를 얻을 수 있다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;</description>
      <category>CUDA</category>
      <author>커피살인마</author>
      <guid isPermaLink="true">https://laboratorium.tistory.com/37</guid>
      <comments>https://laboratorium.tistory.com/37#entry37comment</comments>
      <pubDate>Wed, 7 Dec 2022 23:24:17 +0900</pubDate>
    </item>
    <item>
      <title>CUDA API 함수</title>
      <link>https://laboratorium.tistory.com/36</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA API 아키텍처&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vlo3V/btrSAwFfDxT/UPerRmzDF8c16SeZeLXlQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vlo3V/btrSAwFfDxT/UPerRmzDF8c16SeZeLXlQk/img.png&quot; data-alt=&quot;CUDA API architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vlo3V/btrSAwFfDxT/UPerRmzDF8c16SeZeLXlQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fvlo3V%2FbtrSAwFfDxT%2FUPerRmzDF8c16SeZeLXlQk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;453&quot; height=&quot;394&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CUDA API architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA API는 3가지 그룹으로 이루어진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CUDA Driver API&amp;nbsp;&lt;/li&gt;
&lt;li&gt;CUDA Runtime API&lt;/li&gt;
&lt;li&gt;CUDA Libraries&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA&amp;nbsp; API의 세 그룹은 서로 통신을 하면서 GPU를 제어하여 CUDA 프로그램을 동작시킨다. CUDA&amp;nbsp; API가 다루는 분야는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디바이스 관리&lt;/li&gt;
&lt;li&gt;문맥 관리&lt;/li&gt;
&lt;li&gt;메모리 관리&lt;/li&gt;
&lt;li&gt;코드 모듈 관리&lt;/li&gt;
&lt;li&gt;실행 제어&lt;/li&gt;
&lt;li&gt;텍스처 관리&lt;/li&gt;
&lt;li&gt;OpenGL과 Direct3D 상호 운용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CUDA 드라이버 API&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA 드라이버 API는 가장 낮은 수준에 있는 API로, GPU를 직접 제어한다. 드라이버 API로 프로그램을 구현하면 더 많은 함수 호출과 관리가 필요하다. 드라이버 API는 디바이스를 제어하는 가장 낮은 수준의 명령어이기 때문에 가장 빠르게 OpenGL이나 Direct3D의 자원을 사용할 수 있고, 프로그래머가 원하는 방향으로 자세하고 유연하게 구현할 수 있다. 이 때문에 그래픽 처리에 장점을 가지고 있지만 주의할 점은 CUDA 드라이버 API와 CUDA Runtime API는 서로 배타적이기 때문에 동시에 사용하면 안된다는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CUDA Runtime API 실행시간 API&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA Runtime API는 GPGPU를 목적으로 제공되며 드라이버 API의 상위 레벨에 있다. 이것은 단순하게 드라이버 API와 비교하여 상대적으로 상위 레벨에 있다는 것을 말하며 CUDA Runtime API가 많이 추상화 되어 있는 것은 아니다. 때문에 CUDA Runtime API도 GPGPU를 목적으로 한다면 만족할 만한 수준의 성능을 발휘한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA 드라이버 API의 복잡한 관리를 줄여주고 적은 학습으로 쉽게 사용할 수 있어 실용적이다. CUDA Runtime API는 다시 두가지로 구분할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C 형식의 하위 레벨 함수 (cuda_runtime_api.h)&lt;/li&gt;
&lt;li&gt;C++ 형식의 상위 레벨 함수(cuda_runtime.h)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CUDA&amp;nbsp; 라이브러리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA 라이브러리는 CUDA Runtime API를 이용하여 특정 계산을 편리하게 할 수 있도록 제공한다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CUBLAS : 선형대수학 계산을 위한 라이브러리&lt;/li&gt;
&lt;li&gt;CUFFT : 고속 푸리에 변환을 처리할 수 있는 라이브러리&lt;/li&gt;
&lt;li&gt;수학 함수 : 기본적인 수학 함수들의 집합&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CUDA Driver API와 Runtime API 비교&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Driver API 함수는 일반 C/C++ 언어에 Driver API 라이브러리를 사용한다. 따라서 컴파일도 일반 C/C++ 컴파일러를 사용할 수 있다. 윈도우 환경에서는 Visual Studio에서 사용하는 컴파일러를 이용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 비해 CUDA Runtime API는 C/C++과 유사하지만 약간의 차이가 있는 CUDA C 를 사용한다. 컴파일도 전용 컴파일러인 NVCC를 사용해야 한다. CUDA Driver API를 사용할 때 CUBLAS와 CUFFT와 같은 라이브러리는 사용할 수 없지만 Runtime API를 사용할 때는 라이브러리 지원을 받을 수 있다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;</description>
      <category>CUDA</category>
      <author>커피살인마</author>
      <guid isPermaLink="true">https://laboratorium.tistory.com/36</guid>
      <comments>https://laboratorium.tistory.com/36#entry36comment</comments>
      <pubDate>Thu, 1 Dec 2022 13:20:50 +0900</pubDate>
    </item>
    <item>
      <title>정수 연산 SIMD 프로그래밍</title>
      <link>https://laboratorium.tistory.com/35</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;XMM 레지스터&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SIMD는 한 번에 여러개의 데이터를 연산. XMM 레지스터의 크기는 128-bit으로 32-bit 범용 레지스터 크기의 4배. XMM 레지스터의 개수는 총 8개로 XMM0~XMM7 사용 가능. 64-bit 컴퓨터 프로그래밍에서는 XMM15까지 총 16개 사용.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범용 데이터 레지스터&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EAX : Accumulator&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EDX : Data&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECX : Count&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBX : Base&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESI : Source Index Pointer&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EDI : Destination Index Pointer&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EBP : Base Pointer&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESP : Stack Pointer&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SIMD에는 정수형, 실수형 두가지 병렬 연산 방식이 존재.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669454224153&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;P &amp;lt;SIMD_op&amp;gt; &amp;lt;suffix&amp;gt;
//P :'packed'를 의미하며 정수형 SIMD 명령어. 실수형 SIMD 명령어는 P가 뒤에 붙음. 
//&amp;lt;SIMD_op&amp;gt; : 명령어 종류, &amp;lt;suffix&amp;gt; : 연산을 수행할 pack 사이즈&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XMM 레지스터에 값 대입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;XMM 레지스터는 SIMD 명령어에서 변수라고 볼 수 있음. 고정된 변수 이름으로 XMM0 부터 XMM7까지 8개의 변수를 사용할 수 있음. 해당 레지스터의 크기는 128-bit이다. XMM 레지스터에 값을 대입하는 방법은 메모리 배열의 주소 포인터를 이용해 한 번에 여러개씩 입력.&amp;nbsp;&lt;/p&gt;</description>
      <category>SSE</category>
      <author>커피살인마</author>
      <guid isPermaLink="true">https://laboratorium.tistory.com/35</guid>
      <comments>https://laboratorium.tistory.com/35#entry35comment</comments>
      <pubDate>Sat, 26 Nov 2022 19:08:47 +0900</pubDate>
    </item>
    <item>
      <title>SIMD Introduction</title>
      <link>https://laboratorium.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SIMD : Single Instruction Multiple Data 한번의 연산으로 여러 개의 데이터를 처리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Parallel program model&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SIMD (명령어 수준 병렬 프로그래밍 모델)&lt;/li&gt;
&lt;li&gt;Shared Memory Parallel Programming model - 멀티 스레드로 데이터 처리 (Multithreading, pthreads, OpenMP, CUDA...)&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Message passing Parallel Programming model - 여러개의 PC를 메시지로 통신하여 대용량의 데이터를 처리하는 것. 분산 컴퓨팅, 그리드 컴퓨팅과 같은 의미. (MPI, HPF, PVM...)&lt;/li&gt;
&lt;li&gt;Hybrid Model - 위 세가지 혼합하여 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SIMD 프로그램 구조&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;메모리 변수 Packing - 메모리에 있는 값을 128-bit 데이터형에 입력. 데이터형은 XMM 레지스터, __ㅡ128, Vector 클래스가 될 수 있다.&amp;nbsp;&amp;nbsp;&lt;/li&gt;
&lt;li&gt;128-bit 데이터 병렬 연산 반복 수행 - 루프를 돌면서 반복을 병렬 연산 수행. SIMD 명령어, intrinsic 함수, Vector 연산자로 이루어짐.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;메모리 변수에 출력 - 병렬 수행한 결과를 128-bit데이터형에서 메모리 변수로 꺼내옴.&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;구현 도구의 종류&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어셈블리 언어 - SIMD 명령어&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C 함수 - Intrinsic 함수&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++ - Vector class&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Intel에서 지원하는 Vector Class 는 intrinsic 함수를 wrapping하여 만든 클래스.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pack &amp;amp; Package&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 연산은 128-bit 데이터형 변수에 작은 사이즈의 데이터를 여러 개 담아서 처리. 이러한 구조를 'Packed'라고 하며 128-bit의 package 안에 32-bit pack 4개를 담을 수 있음. 128-bit package 안에 많은 수의 pack을 담을수록 프로그램 효율 증가.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스칼라 계산과 Pack 계산&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SIMD 프로그래밍은 Packed 연산, 스칼라 연산 방식 지원. 스칼라 연산 방식은 최하위 위치에 있는 pack 하나만을 연산하고 나머지 pack은 앞선 인자의 값을 그대로 전달하는 방식. 주로 사용하는 것은 packed 연산이고 package 연산이 모두 끝난 후 1개의 값을 추출하거나 대입할 때 스칼라 연산을 사용.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SSE &amp;amp; AVX (Intel)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSE에서 SSE4로 발전하면서 8-byte 동시 연산 구조에서 16-byte 연산으로 발전. MMX에서 SSE로 진행하며 정수 연산만 가능했던 것이 부동소수점까지 연산 가능해졌음. AVX는 32-byte 동시 연산에 레지스터 개수가 16개.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NEON (ARM)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Q레지스터 (Quad word 128-bit) 16개, D레지스터(Double word 64-bit) 32개를 포함하여 총 256-byte의 레지스터를 사용하여 연산.&amp;nbsp;&lt;/p&gt;</description>
      <category>SSE</category>
      <author>커피살인마</author>
      <guid isPermaLink="true">https://laboratorium.tistory.com/34</guid>
      <comments>https://laboratorium.tistory.com/34#entry34comment</comments>
      <pubDate>Sat, 26 Nov 2022 17:48:11 +0900</pubDate>
    </item>
    <item>
      <title>메모리 아키텍처</title>
      <link>https://laboratorium.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA의 메모리는 GPU의 구조에 따라 계층 구조로 되어 있음. 그래픽 카드에 있는 메모리는 레지스터, 로컬 메모리, 공유 메모리, 글로벌 메모리, 상수 메모리, 텍스처 메모리로 구성되어 있음.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개별 스레드 영역 - 레지스터, 로컬 메모리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동일 블록 내에 있는 스레드 간에 공유 - 공유 메모리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리드를 이루는 모든 스레드 간에 공유 - 글로벌 메모리, 상수 메모리, 텍스처 메모리&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;787&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSXSU0/btrRS4JykQS/U5fblZOXQdbsOQkoDfdbj0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSXSU0/btrRS4JykQS/U5fblZOXQdbsOQkoDfdbj0/img.png&quot; data-alt=&quot;CUDA Memory Model (NVIDIA documentation)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSXSU0/btrRS4JykQS/U5fblZOXQdbsOQkoDfdbj0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSXSU0%2FbtrRS4JykQS%2FU5fblZOXQdbsOQkoDfdbj0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;401&quot; height=&quot;445&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;787&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;CUDA Memory Model (NVIDIA documentation)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액세스 속도 : 글로벌 메모리 &amp;lt; 로컬 메모리 &amp;lt; 공유 메모리 &amp;lt; 레지스터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 용량 : 글로벌 메모리 &amp;gt; 로컬 메모리 &amp;gt; 공유 메모리 &amp;gt; 레지스터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;액세스 범위 : 글로벌 메모리 &amp;gt; 로컬 메모리 &amp;gt; 공유 메모리 &amp;gt; 레지스터&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Register 레지스터&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;On chip processor memory로 직접 연산을 수행하는 가장 빠른 메모리. GPU 한 사이클 이내의 속도로 읽고 쓰기 가능. 커널 함수 안에서 사용하는 로컬 변수는 프로그램 실행 시 레지스터에 값이 저장됨.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Local Memory 로컬 메모리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 함수 내에서 너무 많은 로컬 변수를 사용하거나, 배열형 변수로 큰 용량을 사용하면 프로세서 밖에 있는 DRAM에 메모리가 할당 됨.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 메모리로 할당되는 경우&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;너무 많은 레지스터 변수를 사용했을 때&lt;/li&gt;
&lt;li&gt;너무 많은 로컬 변수를 사용했을 때&lt;/li&gt;
&lt;li&gt;로컬 변수로 배열을 사용했을 때&lt;/li&gt;
&lt;li&gt;커널 함수 안에서 수학 함수(ex) sin(), cos() 등) 를 사용했을 때&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 메모리 이상에 대해서는 글로벌 메모리에 할당하여 사용해야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Shared Memory 공유 메모리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;On chip processor에 있음. CPU 환경에서는 캐시를 자유롭게 사용할 수 없어 성능을 높이려면 변수에 대해 어셈블리어로 레지스터를 재사용하도록 구현해야 함. 그러나 SIMD를 이용해도 레지스터 크기에 제한이 있어 한계가 있음.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA에서는 SM당 16KB 크기의 공유 메모리를 힙(Heap)이나 스택(Stack) 메모리처럼 자유롭게 할당하고 사용할 수 있음. GPU의 SM 안에 공유 메모리가 있고 SM은 블록 단위로 프로그램을 처리하기 때문에 공유 메모리의 데이터는 동일한 블록 안에 있는 스레드 사이에서만 공유할 수 있다. 공유 메모리를 사용하는 방법은 정적으로 할당하는 방법과 동적으로 할당하는 방법이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;정적 할당 방법&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1669207376770&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;__shared__ int a[512]; //정적 할당&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 함수 내에서 공유 메모리 할당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동적 할당 방법&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1669207423059&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extern __shared__ float sdata[]; //동적 할당&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 먼저 선언하고, 커널 함수를 실행할 때 &amp;lt;&amp;lt;&amp;lt; &amp;gt;&amp;gt;&amp;gt; 안에 제 3인자로 크기를 지정할 수도 있음.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669208202359&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//NVIDIA에서 제공하는 sample 프로그램 사용 시에도 외부 라이브러리 C:\ProgramData\NVIDIA Corporation\CUDA Samples\v11.4\common\inc 프로젝트 속성에 추가해야 함.
//Shared Memory R/W
__global__ void LoadStoreSharedMemory(int* In, int* Out)
{
	//2k shared memory allocation
	__shared__ int SharedMemory[512];
	
	//블록 내 스레드로 분할 복사
	SharedMemory[threadIdx.x] = In[threadIdx.x];
	__syncthreads();

	//글로벌 메모리로 스레드 분할 복사 
	Out[threadIdx.x] = SharedMemory[threadIdx.x];
	__syncthreads();
}

int main()
{
	LoadStoreSharedMemory &amp;lt;&amp;lt;&amp;lt;1,512 &amp;gt;&amp;gt;&amp;gt; (dev_In, dev_Out);
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 함수에서 공유 메모리로 값을 읽어오는 방법은 인덱스를 지정하여 SharedMemory[i] = GlobalMemory[i]와 같이 직접 대입하는 방법을 사용. CUDA 프로그램은 멀티스레딩을 이용하여 구현하므로 인덱스 i 를 스레드 아이디 (threadIdx.x)로 지정하여 하나의 스레드가 하나의 배열 인덱스를 읽어서 여러 스레드가 동시에 병렬로 액세스 하도록 함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록 안에서는 여러 스레드가 비동기로 동작하여 공유 메모리에 값을 대입하기 때문에 어떤 스레드는 먼저 완료되고 어떤 스레드는 늦게 진행하게 됨. 공유 메모리의 값이 다 복사되기 전에 다음 단계를 진행하면 쓰레기 값이나 0의 값을 읽어올 수 있기 때문에 __syncthreads() 함수를 이용하여 먼저 완료된 스레드를 대기시켜 동기화 함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 공유 메모리의 크기가 스레드 개수보다 많으면 '공유 메모리 크기/블록 안의 스레드 개수' 비율로 값을 전달. 즉 하나의 스레드가 &lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;'공유 메모리 크기/블록 안의 스레드 개수' 만큼의 배열 인덱스를 복사하도록 함.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;Global Memory&amp;nbsp; 글로벌 메모리&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 메모리는 비디오 카드에 장착된 DRAM 메모리를 의미. 그래픽 카드마다 사용할 수 있는 그로벌 메모리 용량의 차이가 있으며 그 크기는 사양에 표기되어 있다. 글로벌 메모리는 GPU 칩 외부에 있기 때문에 메모리 액세스 속도가 GPU 칩 내부에 있는 레지스터나 공유 메모리보다 많이 느림. 그러나 CPU의 메모리와 비교하면 매우 빠름. 글로벌 메모리를 사용할 때 주의할 점은 메모리 액세스 패턴에 따라 그 속도의 편차가 크기 때문에 프로그램 구현 시에 잘 설계해야 하는 점이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669545733969&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//글로벌 메모리 할당, 해제
cudaError_t cudaMalloc(void** devPtr, size_t count);
cudaError_t cudaFree(void* devPtr);

//호스트 메모리-&amp;gt; 글로벌 메모리, 글로벌 메모리 -&amp;gt; 호스트 메모리 값 복사
cudaError_t cudaMemcpy(void* dst, const void* src, size_t count, cudaMemcpyKind kind);
//kind : 호스트와 디바이스 간의 복사 방향&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 메모리를 확보하고 나서 해제하지 않으면 할당한 커널 함수가 완료되고서 다른 커널 함수가 동작하여도 계속 메모리가 확보되어 있기 때문에 반드시 해제해야 함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Constant Memory 상수 메모리&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수 메모리는 DRAM에 있는 데이터를 읽기 전용으로 사용하며 캐시를 지원함. 상수 메모리로 사용할 수 있는 최대 크기는 64KB이다. 최초로 읽어오는 데이터는 DRAM에서 값을 가져오기 때문에 GPU 클럭으로 400~600 사이클이 소요되지만 한번 캐시에 올라온 값을 반복하여 재사용할 때에는 레지스터와 동일한 속도로 사용할 수 있다. 호스트 영역에서 값을 쓰고 디바이스 영역은 메모리에 있는 값을 읽기만 할 수 있다. 글로벌 메모리처럼 모든 스레드가 공유할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수 메모리를 사용하려면 __constant__ 지시어를 사용하여 상수 메모리 영역을 할당함. 그 다음 cudaMemcpyToSymbol()함수를 이용하여 호스트 영역에서 상수 메모리에 값을 전달. 첫번째 인자는 상수 메모리의 이름을 지정하고 두번째 인자는 상수 메모리에 전달할 값이 들어있는 호스트 영역의 변수 주소. 세번째 인자에는 전달하고자 하는 데이터의 크기 입력. 이렇게 값을 전달하고 난 뒤에는 디바이스 영역의 커널 함수에서 읽기 전용으로 그 값을 사용할 수 있다. 상수 메모리는 64KB 이내에서 읽기 전용으로 반복 사용하는 경우에 글로벌 메모리보다 빠른 효과를 얻을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669546032922&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Constant memory example
__constant__ int cData[6];
int hData[6] = {1,2,3,4,5,6};
cudaMemcpyToSymbol(&quot;cData&quot;, &amp;amp;hData, sizeof(hData));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Texture Memory 텍스처 메모리&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스처 메모리는 캐시 읽기를 지원하며 읽기 전용이다. DRAM에서 한 번 읽어들인 데이터는 캐시에서 재사용되어 빠른 성능을 발휘할 수 있다. 한 번의 메모리 읽기 부하만 있기 때문에 사용할 수 있는 조건을 충족시키면 유용하게 사용할 수 있다. 하지만 텍스처 메모리는 원래 그래픽 전용 기능을 위해 제공되는 메모리이기 때문에 텍스처 캐시는 2D 데이터 사용에 최적화 되어 있다. CUDA SDK 2.2 버전 이후에는 텍스처 메모리 사용을 지원하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메모리 성능 최적화&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NVIDIA 그래픽 카드의 메모리 전송은 한 번에 512-bit 를 읽어올 때 최지대의 성능을 발휘하게 된다. 글로벌 메모리를 읽어올 때 최대 밴드 폭을 사용할 수 있는 조건이 있는데 이것을 메모리 결합(Coalescing) 조건이라 한다. CUDA에서 메모리 액세스를 하는 방법은 워프(32개 스레드)를 절반으로 나누어 16개의 스레드를 동시에 전송하는 것이다. 0~15 번에 해당하는 스레드가 글로벌 데이터를 읽으면 다음에는 16~31번에 해당하는 스레드가 데이터를 읽는다. 가장 많이 사용하는 32-bit 데이터를 16개의 스레드가 전송하는 양을 계산해보면 32x16=512-bit 로, 메모리 밴드 폭을 전부 채울 수 있는 크기가 된다. 글로벌 메모리 결합 조건은 그래픽 카드에 따라 차이가 있다.&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;글로벌 메모리 액세스 결합&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 글로벌 메모리의 변수가 32-bit, 64-bit, 128-bit 형이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-16개의 스레드가 액세스하는 어드레스가 오름차순으로 인접해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-첫 번째 스레드가 액세스하는 메모리의 어드레스가 64byte 또는 128byte 배수의 시작 위치에 있어야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;공유 메모리 뱅크 충돌&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 메모리는 GPU 프로세서 내부에 장착되어 있어 제대로 사용하면 빠른 속도로 데이터를 처리할 수 있는 CUDA의 큰 장점 중 하나이다. 공유 메모리의 속도를 저하하는 요인으로 공유 메모리를 구성하는 메모리 뱅크의 액세스 충돌이 있다. 공유메모리는 뱅크로 이루어져 있으며 compute capability 1.x에서는 16개, compute capability 2.x, 3.x에서는 32개의 뱅크로 이루어져 있다. 각 뱅크마다 한 번의 GPU 사이클에 한번 액세스 할 수 있으며, 뱅크가 16개 있을 때 스레드가 병렬로 16개의 뱅크에 동시에 액세스할 때 가장 큰 효율을 얻을 수 있다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;뱅크 충돌이 없는 공유 메모리 액세스&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 메모리도 32개의 스레드로 구성된 워프를 16개씩 절반으로 나누어 전반부와 후반부로 액세스하게 된다 . 글로벌 메모리와 같은 시작 어드레스나 결합 전송 조건은 없지만 한 스레드당 하나의 뱅크에 액세스할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669634047544&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//뱅크 충돌이 없는 Shared memory access
//커널 함수 
__global__ void AccessSharedMemory(float* gData);
{
	__shared_ float sData[TRHEAD_COUNT];
    sData[threadIdx.x]=gData[threadIdx.x]; //글로벌 메모리 값 전송
    __syncthreads();
    
    float Data;
    Data = sData[threadIdx.x]; //Shared memory access
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뱅크끼리 겹치는 스레드가 없으면 공유 메모리는 효과적으로 동작한다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;2-way 뱅크 충돌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만일 16개의 스레드가 한 번에 공유 메모리를 읽거나 쓰기를 할 때 2개의 스레드가 하나의 뱅크를 액세스하려고 하면 뱅크 충돌이 발생한다. 16개의 스레드에서 2번의 뱅크 충돌이 발생하면 2-way 뱅크 충돌이라고 한다. 2-way 뱅크 충돌이 발생하면 GPU 2사이클에 나누어서 공유 메모리를 차례로 가져오게 되고 1/2로 효율이 떨어지게 된다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669634375587&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//2-way  뱅크 충돌이 발생하는 프로그램
//커널 함수
__global__ void BankCOnflict(float* gData);
{
	__shared__ flaot sDtata[THREAD_COUNT*2];
    ......
    
    float Data;
    Data = sData[threadIdx.x*2]; //2-way 뱅크 충돌
    //threadIdx.x*4 -&amp;gt; 4-way 뱅크 충돌 -&amp;gt; 1/4 효율
    ......
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두개의 스레드만 같은 뱅크에 액세스 해도 효율이 떨어지는 것은 똑같음.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;16-way 뱅크 충돌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;16-way 뱅크 충돌은 1차원으로 구성된 스레드-블록 구조에서는 잘 발생하지 않는다. 하지만 2차원 스레드-블록 구조로 작업을 분할할 때 스레드 인덱스 처리를 자세히 살펴보지 않으면 발생할 수 있다. 16개의 스레드가 16x16으로 구성된 공유 메모리에 열 방향으로 액세스 하면 16-way 뱅크 충돌이 발생하게 되고 이것을 16회 반복하게 된다.메모리 패딩을 통해 메모리 할당 공간을 수정하여 뱅크 충돌을 방지할수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Pinned Memory 고정된 메모리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Page Lock Memory 또는 Pinned Memory(고정된 메모리)라 부르는 시스템 메모리는 가상 메모리(Virtual Memory)기술과 관련이 있다. 가상 메모리는 컴퓨터에 장착된 RAM의 용량에 한계가 있어 충분하지 않기 때문에 메모리 부족 문제를 해결하기 위해서 개발되었다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영체제 시스템이 RAM과 disk의 공간을 이용하여 가상으로 큰 메모리 공간을 페이지 단위로 분할하여 제공한다. 운영체제는 현재 실행되는 메모리를 물리적인 RAM에 올리고 이후에 사용되거나 이미 사용된 메모리는 disk에 저장하는 방식으로 동작한다. 이렇게 물리적 RAM 공간과 disk의 가상 메모리 공간을 페이지 단위로 치환하면서 RAM보다 큰 공간의 가상 메모리를 현재 실행되는 모든 응용프로그램에 제공하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA프로그램은 호스트에서 디바이스 또는 디바이스에서 호스트로의 복사 과정이 추가되어 이 시간을 줄이고자 CUDA 프로그램을 구현할 때 여러 가지 방법을 사용하게 된다. 그 중 한가지가 가상 메모리 기술의 일부를 제한하고 물리적인 RAM 공간만을 사용하지 않는 것이다. 가상 메모리 기술을 이용하면 메모리를 사용하기 위해 disk와 물리적인 RAM 공간의 페이지 치환이 일어나게 되는데, 이 시간을 줄이고 물리적인 RAM에서 디바이스로 바로 복사를 하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 페이지 치환이 되지 않는 메모리를 Page Lock Memory 또는 Pinned Memory라고 부르는데 이것은 RAM 공간에만 상주하는 메모리를 의미한다. CUDA에서 스트림(비동기 함수)을 사용하려면 고정된 메모리를 이용해야 한다. 고정된 메모리를 너무 많이 사용하면 컴퓨터의 가상 메모리가 원활하게 작동하기 어려워서 메모리 성능과 전체 시스템의 성능 저하를 가져올 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669639822838&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cudaError_t cudaMallocHost(void **ptr, size_t size); //첫번째 인자에 고정된 메모리를 할당하여 돌려주고 두번째 인자에는 할당하고자 하는 크기를 바이트 단위로 입력
cudaError_t cudaFreeHost(void *ptr); //첫번째 인자로 메모리 포인터 입력하여 해제&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기타 메모리 사용&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;Zero copy 제로 복사&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Zero copy 또는 Mapped Memory 기능은 단어 뜻 그대로 복사를 하지 않는다는 의미이다. 대부분의 CUDA 프로그램은 GPU를 사용하기 위해 입력 데이터를 호스트에서 디바이스로 복사하고 그 데이터를 처리하여 출력 데이터를 디바이스에서 호스트로 복사한다. 이는 데이터 읽기와 계산, 쓰기의 과정이 차례로 실행되며 데이터 전송 시 사용되는 PCI 버스를 한 방향만 사용하게 된다. 제로 복사는 GPU가 호스트에 할당된 고정된 메모리(Pinned Memory)영역에 바로 액세스 하여 데이터를 읽고 쓰는 작업을 한다. PCI 버스를 이용하여 데이터를 전송하는 것은 동일하기 때문에 전송 속도가 빨라지는 것은 아니지만. 메모리에서 읽어들인 데이터를 계산하고 결괏값을 메모리에 쓰면 비동기로 양방향 PCI 전송이 진행되기 때문에 그만큼의 성능 향상을 얻을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다만, 이런 성능 향상의 효과를 얻으려고 Mapped Memory를 사용할 때 글로벌 메모리의 결합 전송(Coalescing)과 동일한 조건을 커널에서 충족시켜야 한다. 만일 커널에서 작은 크기의 데이터를 많은 횟수로 Mapped Memory를 액세스 하게 되면 통상적인 데이터 전송보다 떨어지는 효과를 얻을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669640469787&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Zero copy(Mapped Memory) 
cudaError_t cudaHostAlloc(void ** pHost, size_t size, unsigned int flags) 
//flags 옵션- cudaHostAllocMapped 지정 필요 
//호스트 측에 Mapped Memory가 할당되면 디바이스 메모리 영역에서 사용할 포인터 변수 준비해야 함.

cudaError_t cudaHostGetDevicePointer(void **pDevice, void *pHost, unsigned int flags)
//pDevice : 디바이스에서 사용할 포인터 변수
//pHost : 호스트 측에서 할당한 메모리 포인터&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;Portable Pinned Memory 포터블 고정 메모리&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고정된 메모리를 이용한 제로 복사는 싱글 스레드 영역에서 유효. 하나의 GPU로 구성된 PC에서는 큰 불편함 없이 Pinned Memory(고정된 메모리)를 사용할 수 있으나 복수의 GPU로 구성된 PC에서는 문제가 될 수 있음. 동시에 2개 이상의 GPU를 구동시키려면 두 개 이상의 호스트를 스레드를 생성하여 처리하게 되는데 이때 하나의 스레드에서 생성한 고정된 메모리는 다른 스레드에서 사용할 수 없게 됨. 이러면 한정된 시스템 자원인 고정된 메모리가 낭비됨. 이를 피하고자 사용하는 것이 Portable Pinned Memory(포터블 고정 메모리)이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1669866436717&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//Portable Pinned Memory
cudaError_t cudaHostAlloc(void ** pHost, size_t size, unsigned int flags) 
//flags 옵션- cudaHostAllocMapped | cudaHostAllocPortable 옵션 지정 필요&lt;/code&gt;&lt;/pre&gt;</description>
      <category>CUDA</category>
      <author>커피살인마</author>
      <guid isPermaLink="true">https://laboratorium.tistory.com/33</guid>
      <comments>https://laboratorium.tistory.com/33#entry33comment</comments>
      <pubDate>Tue, 22 Nov 2022 21:06:26 +0900</pubDate>
    </item>
    <item>
      <title>스레드 스케줄링 Thread Scheduling</title>
      <link>https://laboratorium.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Thread Context Switching&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GPU는 대량의 레지스터를 지원하므로 스레드 교환을 위해 할당할 공간을 더 많이 가지고 있어 스레드를 많이 생성해도 부담이 적음. 동시에 처리할 수 있는 스레드의 수보다 많은 스레드를 이용하여 프로그램을 구성해도 CPU와 같이 동작하지 않음. Thread Context Switching을 위해 레지스터와 메모리 간의 스위칭 교환으롭 라생할 부하를 막도록 최대 생성 가능 스레드만 생성하고 나머지 스레드는 대기시킴. 최대 스레드 개수 만큼 처리하고 난 뒤 나머지 스레드를 다시 생성하여 스레드 컨텍스트 스위칭을 위한 부하를 방지. 이를 블록과 워프 단위로 처리.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SP의 스레드 스케줄링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA의 최소 단위 코어인 SP(Streaming Processor)는 한 번에 4개의 스레드를 실행하여 처리. 물리적으로 하나의 코어가 4개의 스레드를 동시에 실행시키는 것이 아니라 시분할을 이용하여 4개의 스레드 단위로 프로그램 처리. SP도 하나의 프로세서이므로 클록 단위로 실행. 4개의 스레드를 처리하기 위해 4번ㄴ의 클록을 하나의 사이클로 동작하면서 처리.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;워프&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 SM에서 처리되는 스레드. 코드 진행의 단위가 됨.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SM의 스레드 스케줄링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA 연산 처리는 코드의 위치를 워프 단위로 동시에 처리. 레지스터에 있는 변수에 대한 연산은 1개의 워프 단위로 처리하고 메모리에서 레지스터로 값을 로딩하는 작업은 워프를 절반으로 나누어 진행.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SM의 워프 스케줄링&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SM에서 워프 단위(한번에 처리할 수 있는 스레드 수)로 코드를 실행할 때 레지스터에 로딩된 변수를 연산하는 명령은 적은 클록수로 계산 가능. 그러나 실제로 사용하는 프로그램은 메모리에서 레지스터로 데이터를 읽어오고 레지스터의 값을 메모리에 쓴느 연산이 많이 들어있음. 이런 연산이 수행될 때 SM은 대기상태에 놓이므로 시간을 낭비하지 않기 위해 SM은 워프 단위로 스케줄링하게 됨.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널의 코드가 메모리를 읽고 쓰게 되면 해당 작업을 진행하는 동안 또 다른 워프를 스위칭하여 연산을 수행. 이렇게 스위칭하면서 관리하는 워프 개수가 많을수록 SM이 메모리를 읽고 쓰는 시간 동안 대기하는 시간이 줄어들고 효율적으로 동작.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Active Block&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SM이 코드를 실행하면서 관리하는 블록. CUDA 프로그램의 효율은 액티브 블록이 많을수록 더욱 효율적이게 되며 이것은 커널 함수의 레지스터 사용이나 공유 메모리의 사용량으로 결정됨. &amp;nbsp;&lt;/p&gt;</description>
      <category>CUDA</category>
      <author>커피살인마</author>
      <guid isPermaLink="true">https://laboratorium.tistory.com/32</guid>
      <comments>https://laboratorium.tistory.com/32#entry32comment</comments>
      <pubDate>Tue, 22 Nov 2022 20:43:13 +0900</pubDate>
    </item>
    <item>
      <title>스레드 블록 아키텍처 Thread Block Architecture</title>
      <link>https://laboratorium.tistory.com/31</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;CPU의 스레드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU환경에서 생성하는 스레드는 생성 즉시 실행 코드가 프로세서에서 실행된다. 생성한 스레드의 개수와 CPU 코어 수가 동일하면 최적의 효율을 나타냄.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU의 태스크 분할 처리 과정&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;스레드의 개수를 코어 수와 동일하게 생성&lt;/li&gt;
&lt;li&gt;해야 할 작업을 태스크 단위로 작게 분할하여 태스크 큐에 넣는다.&lt;/li&gt;
&lt;li&gt;스레드가 작업을 태스크 단위로 가져와서 처리&lt;/li&gt;
&lt;li&gt;스레드가 하나의 테스크를 완료하면 다음 태스크를 큐에서 가져와서 다시 작업을 수행&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그리드 블록 모델 Grid Block Model&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CUDA에서는 스레드가 모여 블록을 이루고 블록이 모여 그리드를 이루게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 블록은 1개에서부터 최대 512개까지 스레드를 가질 수 있다.&amp;nbsp; 블록 안에 있는 스레드는 고유 아이디를 갖는다. 블록이 스레드를 512개까지 가지고 있을 수 있기 때문에 0번부터 511번까지 고유한 인덱스 번호를 지정받게 된다. 블록 안에 스레드를 배치하는 방법은 1차원, 2차원, 3차원으로 지정할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드 인덱스는 'threadIdx'로 이름이 지정된 변수를 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1668681617309&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//1차원으로 배치된 스레드 6개
//thread 0 : threadIdx.x = 0
__global__ void kernel&amp;lt;&amp;lt;&amp;lt;1,6&amp;gt;&amp;gt;&amp;gt;(int a, int b, int c);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;CUDA 스레드 모델에서 SP는 4개의 스레드를 동시에 실행할 수 있고, 하나의 블록은 하나의 SM과 대응하여 동작하게 된다. 하나의 블록에서 스래드를 생성시켜 프로그램을 실행시키면 1개의 SM에서만 동작하여&amp;nbsp; GPU의 성능을 전부 발휘하지 못하고 비효율적이 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드 블록의 개수 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 함수 호출 문법&lt;/p&gt;
&lt;pre id=&quot;code_1668684832746&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;__global__ void kernel&amp;lt;&amp;lt;&amp;lt;Dg, Db, Ns, S&amp;gt;&amp;gt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dg(Dimensions of the grid: type dim3)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리드의 크기로 그리드 안의 블록 개수를 3차원적으로 지정.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Db(Dimensions of the block : type dim3)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록의 크기로 블록 안의 스레드 개수를 3차원적으로 지정.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Ns(Number of bytes shared memory dynamically allocated/ block : type size_t)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 블록에서 사용하는 공유 메모리의 크기 지정. 정수형으로 입력해야 하며 생략 가능. 생략하면 디폴트 값 0 대입&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;S(Associated cudaStream_t)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스트림은 비동기 처리를 하려고 사용. 실행 스트림 번호를 '0'이상의 정수로 지정. 생략 가능, 생략 시 스트림 번호 '0'으로 그 커널 함수가 실행된다. 스트림 번호 '0'은 비동기 처리를 하지 않고 동기 구동을 하는 것을 의미.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그리드&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리드 : 블록의 모임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블록 : 스레드의 모임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드 -&amp;gt; 블록 -&amp;gt; 그리드&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리드는 커널이 실행될 때 스레드를 구성하는 최상위 집합체로 디바이스에서 커널이 실행되는 모듈, 응용 프로그램을 나타냄. 하나의 디바이스에서 실행되는 그리드는 동일한 시점에 하나만 존재할 수 있음.&amp;nbsp;&lt;/p&gt;</description>
      <category>CUDA</category>
      <author>커피살인마</author>
      <guid isPermaLink="true">https://laboratorium.tistory.com/31</guid>
      <comments>https://laboratorium.tistory.com/31#entry31comment</comments>
      <pubDate>Wed, 16 Nov 2022 20:58:02 +0900</pubDate>
    </item>
    <item>
      <title>CUDA C</title>
      <link>https://laboratorium.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;함수 수식어&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;__global__&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디바이스에서 실행. 호스트에서 호출할 수는 있어도 디바이스에서 호출할 수는 없음. 디바이스로 실행하는 커널 함수 지정에 사용할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재귀호출 할 수 없음. 가변형 인수를 가질 수 없음. __host__와 동시에 이용할 수 없음. 디바이스에서 처리가 완료되기 전에 호출한 즉시 반환하여 비동기 동작함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;__device__&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;디바이스에서 실행. 디바이스에서 호출할 수 있고 호스트에서 호출할 수 없음. 디바이스 코드 중에 작성하여 디바이스 내에서의 실행되는 서브함수로 사용.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1668599006683&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;__device__ int function(int a, int b)
{
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의사항 : 재귀호출 할 수 없음. 함수 내에 static 변수를 가질 수 없음. 가변형 인수 가질 수 없음. __device__로 지정한 함수의 포인터는 사용할 수 없음.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;__host__&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호스트에서 실행. 호스트에서 호출할 수 있고 디바이스에서 호출할 수 없음. 호스트에서 보통 사용하는 함수가 됨.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1668599110112&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;__host__ int function(int a, int b)
{
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의사항 : __host__, __global__, __device__가 지정되지 않은 경우, __host__를 지정한 것과 동일. __global__과 동시에 사용할 수 없음. __device__와 동시에 사용하여, 호스트와 디바이스 양쪽에서 사용할 수 있는 함수로 작성 가능.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;변수 수식어&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;__device__&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글로벌 메모리 영역에 할당되어 프로그램이 종료될 때 까지 유효. 모든 스레드가 액세스할 수 있고 호스트 측에서는 API함수를 통해 읽기와 쓰기가 가능.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;__constant__&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수 메모리( Constant Memory)영역에 할당되어 프로그램이 종료될 때까지 유효. 모든 스레드가 액세스 하며 읽기만 가능. 호스트에서 cudaMemcpyToSymbol()의 API를 통해서 값을 쓸 수 있음. 상수 캐시(Constant Cache)가 함께 사용됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;__shared__&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 메모리 영역에 할당되어 실행 중인 스레드 블록 상에서 유효. 블록 내의 스레드는 액세스 하여 읽고 쓰기가 가능.&lt;/p&gt;</description>
      <category>CUDA</category>
      <author>커피살인마</author>
      <guid isPermaLink="true">https://laboratorium.tistory.com/30</guid>
      <comments>https://laboratorium.tistory.com/30#entry30comment</comments>
      <pubDate>Wed, 16 Nov 2022 20:49:30 +0900</pubDate>
    </item>
  </channel>
</rss>