CUDA Programming Applications

کاربردهای برنامه نویسی کودا

CUDA Programming Applications

کاربردهای برنامه نویسی کودا

نصب کودا در ویندوز

نصب کودا در ویندوز

در ابتدا بهتر است اطمینان حاصل کنید که کارت گرافیک کامپییوترتان مبتنی بر کودا ست به یکی از روش زیر می توانید اقدام کنید

1-کیلدهای ترکیبی win+R  به منظور اجرای پنجره Run همزمان فشار دهید کلمه dxdiag  را تایپ کنید پنجره Directx  properties  باز میشود . تب ِDisplay  را اتنخاب کنید در این قسمت به شما مشخصات کارت گرافیک تان را نشان میدهد .

شکل 4

2- پنجره Control Panel  را باز کنید ((Start - Control Panel برروی سیستم دو بار کلیک نمایید در پنجره properties برروی تب HaedWard  سپس Device Manager کلیک کنید . لیستproperties   Directx را باز کنید در نتیجه نام و مدل کارت گرافیکتان را خواهید یافت .

 

(شکل 5)

نیازمندی های سیستم در کودا

برروی استفاده از کودا بروی سیستم تان مورد زیر بایستی نصب شده باشد :

1-یک GPU مبتنی بر کودا

2-یک ورژن از ویندوز ساپورت شده (از Windows Seven  به بعد )

3- یک ورژن از Visual Studio ساپورت شده  از VS2010به بعد

4-ابزار nvidia cuda که در سایت cuda – downloads  موجود می باشد .

می توانید برنامه cuda  را با Proxy از سایت NVida.com ویا برخی از نسخه ها را از softgozer تهیه کنید که حجم ان با برنامه  Nsight  در حدود 1.02گیگابایت می باشد .جهت نصب با توجه به نسخه دانلود شده cuda  برروی بستر OS دلخواه که در اینجا Version 8.0.4.4  برWindows 10  میباشد مراحل زیر را انجام می دهیم : لازم به ذکر است که قبل از نصب  Cudaمی بایست  برروی سیستم Visual Studio نصب شده باشد که برنامه Cuda به عنوان یکی از Feature ها روی ان بار گذاری شود . پس از دریافت از سایت softgozer فایل Cuda -8.0.4.4 Win10.exe  را اجرا می نماییم که در ابتدا در مسیر جهت Extract برنامه  CUDA سوال می کند.


شکل 6

پس ار ان شروع به Extract  میکند

شکل 7

پس از اتمام شدن قسمت Extract بصورت  AutoRun  برنامه Setup Cuda    اجرا می شود و شروع به چک کردن سیستم در رابطه با OS   .وکارت گرافیک می کند البته دقت داشته باشید شاید بعضی مواقع Cuda   بروی سیستم ناسازگار نصب شود اما هنگام اجرای برنامه به مشکل برخورد می نمایید .

شکل 8

وپس از چک کردن سیستم چنانچه به مشکل برخورد نکنید پنجره ذیل ظاهر می گردد

شکل 9

که با انتخاب گزینه Accept  و زدن دو عدد  next  شروع به نصب Package cuda  می کند

شکل 10

پس از اتمام نصب Cuda  برنامه Visual Studio را اجرا نمایید و با انتخاب گزینه  New Project  سمت چپ منو nvidia  و زیر منوی Runtime 8.0 Cuda  ایجاد شده است که با انتخاب ان و تعیین نمودن نام پروژه و Solution Name  گزینه ok  را اجرا می نماییم

شکل 11

پس از انتخاب گزینه ok شروع به ساخت پروژه می نماید و یک Sample  جمع دو برابر 5 عنصری را ایجاد می نماید که میتوان از ان جهت اموزش Cuda استفاده خیلی مفید نمود.

شکل 12

دستورات مهم مدیریت حافظه در  CUDA

معماری کودا

اصطلاحات در کارتهای گرافیکی

 Shaders ها برنامه های کوچکی هستند که میتوانند روی GPU  اجرا شوند . این برنامه ها روی بلاک های GPU پخش می شوند و به صورت موازی اجرا می شوند . و در دو نوع Vertex  و Pixcel  وجود دارد . کارت های گرافیکی از واحدهای سایه زنی Vertex Shader  و Pixel shader استفاده می کنند .

سایه زن راس : Vertex Shader : در پردازندهای گرافیکی واحدی وجود دارد که وظیفه ی ساخت پیکره بندی و اسکلت اجسام را برعهده دارد . این واحد سایه زن راس نام دارد . هر چه تعداد این واحدها بیشتر باشد . پردازنده یگرافیکی قدرتمندتر عمل میکند . در کارت های گرافیک جدید . سایه زن جای خود را به پردازندهای جریانی واگذار کرده است که به انها SM  یا Streaming Multiprocessor  گفته میشود . این برنامه بیشتر در autocad,corel استفاده می شود .

سایه زن پیکسل PIXCEL SHADER :  : پردازندهای گرافیکی برای ان که بتواند به اجسام ساخته شده توسط سایه زن روح ببخشد و ان ها را در دنیای واقعی نزدیکتر کنند به واحدی به اسم سایه زن پیکسل نیاز دارند . در حقیقت سایه زن پیکسل رنگ و میزان نور هر یک از پیکسل های اجسام ساخته شده توسط سایه زن راس را تعیین میکند . این سایه زن موجب ایجاد اثرات . سایه ها .روشنایی .ماتی ودیگر پدیده های یک تصویر می شود .به دلیل نقش مهمی که این سایه زن در یک پردازنده گرافیکی دارد. عموما تعداد انها نسبت به تعداد سایه زن راس بیشتر است . به عنوان مثال کارت گرافیک GeForce 7800GTX دارای 48 عدد سایه زن پیکسل و هشت پردازنده ی راس می باشد.

پردازنده ی جریانی : Stream Multiprocessor : پردازنده های گرافیکی مبتنی بر Directx  نسخه ی 10 شامل تغییرات عمده ای در معماری شدند .در این پردازنده های گرافیکی سایه زن و پیکسل حذف شدند و پردازنده جریانی جایگزین انها شدند . هر پردازنده جریانی تقریبا مشابه با یک پردازنده ی بسیار کوچک است و به تنهایی وظایف مربوط به سایه زن های راس و پیکسل را انجام می دهد .پردازنده های گرافیکی شرکت NVIDIA از سری 8000 به بعد و شرکت ATI از 2000 به بعد دارای این پردازنده های جریانی هستند . به عنوان مثلا پردازنده ی گرافیکی شرکت انویدیا با نام GEFORCE GTX 285 دارای 240 پردازنده ی جریانی هست که هر یک به تنهایی می تواند وظایف سایه زنی راس و پیکسل را انجام دهد.)شکل شماره 10(

8- برنامه نویس ناهمگن

برنامه نویسی ناهمگن گونه ای از برنامه نویسی است که در ان قست های مختلف کد تولید شده . برروی گونه های مختلفی از پردازنده اجرا میشود . به عنوان مثال می توان کدی را تصور کرد که برخی از قسمت های ان بر روی CPU  و برخی از اجزای دیگر ان بر روی پردازنده های کارت گرافیکی اجرا می شوند . این مفهوم می تواند کمک شایانی به بهره بری بیشتر از توان محاسباتی رایانه ها کند. چرا که می توان از همه ی ظرفیت ها پردازشی یک سامانه به طور همزمان استفاده کرد .بدیهی است که برای استفاده حداکثری از این ظرفیت باید چار چوب فراهم شودکه توسعه چنین کدهایی را تسهیل نماید . معماری CUDA  چار چوبی مناسب برای توسعه ی برنامه ی ناهمگن است . این معماری بستری را فراهم می کند که به کمک ان می توان کدهایی ایجاد کرد که قطعه های مختلف ان توانایی اجرا شدن بر روی پردازنده های کارت های گرافیکی و یا پردازنده های اصلی رایانه را دارند . در این معماری دو سکوی اجرایی با نام های host platform   device platform  درنظر گرفته شده است . سکوی host شامل یک یا چند پردازنده ی معمولی یا چند هسته ای د رکارت های گرافیکی NVIDIA است و قطعه ی کد مربوط به ان در نخ های اجرایی بسیار سبک و به صورت موازی اجرا می شوند شکل 11 این دو سکوی اجرایی را نشان می دهد

کدی که بر روی سکوی HOST اجرا می شوند یک کد عادی است و با زبان های معمول برنامه نویسی مانند C نوشته و کامپایل می شوند .اما کد دستوری device  به علت ساختار اجرایی متفاوتش APIها و نیز دستورات سطح بالای خاصی دارد بنابراین باید با کامپایلری مناسب و متفاوت از کامپایلرهای عادی کامپایل شود . انم این کامپایلر nvcc  است که به همراه تمامی ان چیزی که برای توسعه کد های device  مورد نیاز است در یک بسته به نام CUDA Toolkit   گرد اوری شده است

 

 

 

 

شکل13:اجرای برنامه ها توسط پردازنده های مختلف در قالب یک برنامه

 

9- محاسبات ناهمگن در پردازنده گرافیکی

در یک دسته بندی کلی . محاسباتی را که در یک رایانه انجام می شود می توان به دو دسته موازی (همزمانی ) و سریال (همگامی ) تقسیم کرد . منظور از محاسبات موازی محاسباتی است که از یکدیگر مستقل بوده و امکان انجام انها به صورت همزمان وجود دارد . محاسبات سری بر خلاف محاسبات موازی به یکدیگر وابسته بوده و باید به ترتیب و یکی پس از دیگری انجام شوند . پردازنده مرکزی به لحاظ دارا بودن برخی از خصوصیات فنی از نظیر حافظه کش و انشعابهای زیاد توانایی بالایی برای انجام محاسبات سری را  دارد . برعکس پردازنده گرافیکی به دلیل توانایی هایی بالایی که در انجام محاسبات شناور دارد  برای محاسبات موازی بسیار ممکن است و می تواند انها را بسیار سریعتر انجام دهد . بتابراین به نظر میرسد که برای رسیدن به بالاترین راندمان باید از پردازنده مرکزی در محاسبات سری واز پردازنده گرافیکی در محاسبات موازی استفاد کرد . محاسبات ناهمگن همین انتخاب درست پردازنده برای هر نوع از محاسبات و عملکردها می باشد .مدل محاسبات پردازنده گرافیکی استفاده از یک CPUو یک GPU  به همراه یکدیگر در یک مد پردازشی ناهمگن است . قسمت ترتیبی کاربرد روی  CPU   وقسمت سنگین محاسبات روی GPU  اجرا می شود . از دیدگاه کاربر کاربد ها سریعتر اجرا می شوند زیرا ازکارایی بالای GPU و موازی سازی در افزایش کارایی استفاده شده است .CPU  و GPU   فضا های حافظه های مجزایی دارند و هیچ دسترسی مستقیمی به حافظه GPU وجود ندارد. برای اینکه کاربردها بتوانند از حافظه GPU استفاده کنند باید داده ها ابتدا در حافظه GPU ذخیره شوند . کد میزبان (CPU) انتقال داده به حافظه دستگاه (GPU ) را مدیریت می کند و سپس محاسبات موازی روی GPU  انجام میشود . کد میزبان مسئول تخصیص و باز پس گیری حافظه از GPU  است یکی از مدهای عملیاتی GPU  یه صورت شکل زیر است .( شکل شماره 14)

1-داده از حافظه اصلی CPU  به حافظه GPU  کپی میشود

2- CPU دستور العمل های پردازشی را برای GPU  ارسال می دهند

3- هسته های GPU دستور العمل هایی را به صورت موازی انجام می دهند .

4- نتیجه از حافظه GPU به حافظه اصلی کپی می شود .

 

10- معماری داخلی هسته کودایی یک کارت گرافیک

GPU های Fermi حداکثر دارای 512 هسته کودایی هستند که هر هسته کودایی در هر کلاک یک دستور العمل اعشاری با دقت تکی یا یک دستور العمل صحیح را به ازای هر thread اجرا می کند . فرکانس کاری فرمی حدود 1.5 گیگا هرتز می باشد . یعنی می توان 1.5 میلیارد عملیات ممیز شناور را در هر ثانیه انجام داد . این 512 هسته کودایی به 16 گروه تقسیم می شوند که به هر گروه یک(Streaming Processor) SM گفته میشود .بنابراین هر SM   دارای 32 هسته کودا خواهد بود که به ان پردازنده کودا نیز گفته میشود . در شکل 15 SM ها در صورت مستطیل های سبز رنگ نمایش داده شده اند و هر کدام شامل 32 هسته هستند . داخل هر بسته کودا یک ALU   صحیح و یک ALU   واحد ممیز شناور قرار دارد هر کدام از این 16 عد SM    یک کش L2  با ظرفیت 768 کیلو بایت احاطه می کند. هر SM  دادای بخش های کش . رجیستر Warp Dispatch  می باشد که دو بخش انتهایی وظیفه اماده شدن برای پردازش بعدی را به عهده دارد.

شکل شماره 15:ساختار داخلی هسته کودایی کارت گرافیک شرکت NVIDIA با معماری فرمی

داخل هر SM  دارای 4 ستون است که شامل 32 هسته کودا،16واحد لود و ذخیره (LD /SD) 4 واحد توابع خاص SFU  می شود واحد لود و ذخیره ادرس های مبدا و مقصد را برای 16  Thread    در هر کلاک محاسبه می کند . واحد SFU  انواع خاصی از دستور العمل ها را مثل کسینوس و جذر را محاسبه می کند . هر SUF   نیز می تواند یک دستور العمل را در هر کلاک اجرا کند.حداکثر 32Thread موازی در هر SM  زمان بندی می شوند تا اجرا شوند که به این کار WARP  گویند. این زمان بندی در واحدی به همین نام یعنی WARP انجام می شود .هرSM   دارای 2 واحد زمانبندیWARPو دو واحدDispatch  است و این پیکربندی می تواند دو Warp  را همزمان اجرا کند  GPU های فرمی داری 6 کنترلر حافظه 64 بیتی هستند که در شکل 15 به صورت DRAM نشان داده شده است .این کنترلرها، دادها را به هسته های CUDA تحویل می دهند

شکل شماره 16:معماری داخلی یک SM

11- نحوه انجام محاسبات در GPU

عمدتا محاسبات مربوط به کاربردهای گرافیکی شامل محاسبات ریاضی ماتریسی است زیرا در هر لحظه نیاز به پردازش ماتریس پیکسل های تصویر داریم . بدیهی است که با افزایش میزان تفکیک پذیری تصاویر حجم این ماتریس ها بزرگ می شود و اگر بخواهیم کل محاسبات ماتریسی خود را به تنهایی و توسط پردازنده های اصلی سیستم انجام دهیم امکان ناتوانی پردازنده درپاسخ به نیازهای برنامه های گرافیکی وجود خواهد داشت . لذا برای غلبه براین مشکل کارت های گرافیکی پای به عرصه وجود گذاشتند . هرکارت گرافیکی حاوی تعداد نسبتا زیادی ریز پردازنده است که به صورت موازی کار می کنند و هر کدام از ان قسمتی از ماتریس تصویر را پردازش می کند . به عبارت بهتر در پردازش یک تصویر خاص کل ماتریس ان تصویر به تعدادی زیر ماتریس کوچکتر تقسیم شده و محاسبات انها به صورت موازی ودر نخ های پردازشی بسیار سبک صورت میگیرد . مثلا به جای اینکه محاسبات مربوط به یک ماتریس 400*400 را به پردازنده ی اصلی واگذار کنیم میتوان این ماتریس را به 1600 زیر ماتریس کوچک 10*10 تقسیم کرد و هر کدام از این ماتریس های کوچک را در یک نخ اجرایی سبک و برروی ریز پردازندهای کارت گرافیکی به صورت موازی اجرا کرد .

پردازنده های گرافیکی، پردازنده های چند هسته ی بسیار موازی هستند و در گروه کامپیوترهای موازی یک دستور العمل چند داده SIMD  قرار دارند . در پردازنده های گرافیکی یک عملیات یکسان به طور همزمان روی چند داده اجرا می شود . درحال حاضر میان افزار کودا . پرکاربردترین ابزار برنامه نویسی بر پایه پردازنده گرافیکی است . کودا یک مدل برنامه نویس موازی ++C/C  است واز مدل برنامه نویسی سریال موازی ناهمگن پیروی میکند . کودا به CPU های چند هسته ای نیز به خوبی نگاشت میشود . کودا مجموعه های از کرنل ها و یا توابع کامپایلر nvcc  کتابخانه های پیشتیبان و درایورهای سخت افزاری را ارائه میدهد .فناوری کودا سرعت عملکرد پردازش گرافیکی را از طریق تحت کنترل در اوردن قدرت GPU امکانپذیر میکند. این فناوری اجازه میدهد صدها تراشه گرافیکی مجزای انویدیا در کنار هم به پردازش موازی اطلاعات بپردازد . مدل برنامه نویسی کودا . کاملا روی موازی سازی داده تمرکز دارد . کودا با ارائه انتزاع های برنامه نویسی سبک  وزن و مناسب . به برنامه نویسان اجازه میدهد تا کرنلها را با استفاده از نخ ها اجراء کنند .در زمان اجراء این نخ ها . به مجموعه ای از بلوک ها که شامل دهها نخ هستند و به طور همروند با هم کرده و منابع را به اشتراک میگذارند .بسط داده می شوند هر چند کودا به دنبال انتزاعی کردن جزئیات سخت افزار است اما اگاهی از معماری سخت افزار مورد استفاده هنوز هم برای برنامه نویسانی که کارایی برایشان اهمیت زیادی دارد مهم است . برای ایجاد پیاده سازیهای کار امد روی پردازنده گرافیکی به درک کامل معماری مورد استفاده . مدل نخ و مدل حافظه نیاز است .

12- کرنل ، نخ و روند اجرایی انها در  GPU

کرنل . یک تابع مانند توابع نرمال زبان C  است که روی ماشین میزبان یا همان CPU   فراخوانی شده و روی ماشین دستگاه یا همان GPU اجرا می شود . در فراخوانی یک کرنل . نخ های زیادی اجرا شده و امکان محاسبات موازی را فراهم می نمایند . نخها در GPU  به صورت همزمان یک دستور یکسان را روی داده های متفاوت اجراء می کنند . یک کرنل در کودا توسط ارایه ای از نخ ها اجرا میشود . به تعداد نخ های تعیین شده در کرنل از تابع کپی گرفته شده و به هر نخ یک کپی از تابع داده میشود تا بتواند دستور را روی داده ی خود اجرا نماید . در طول اجرای یک کرنل .همه نخ ها کد یکسانی را اجرا می کنند .هر نخ . دارای شناسه (ID)   است که از ان برای محاسبه ادرسهای حافظه و تصمیمات کنترلی استفاده می شود . می توان گفت که نخ ، اجرایی از یک کرنل با یک شناسه یا شاخص خاص است هر نخ ، از شاخص خود برای دسترسی به عناصری که در یک مجموعه هستند . استفاده می کتد .مهمترین ویژگی های نخ های کودا عبارتند از :

1-      نخ های بی اندازه سبک وزن هستند

2-     سربار ایجاد انها بسیار کم است

3-     از سوئیچ کردن فوری پشتیبانی می کنند.

روند اجرای نخ در سخت افزار پردازنده گرافیکی طی مراحل زیر انجام می شود :

1-      فراخونی کرنل با دستوری مشابه با KernelFunction////dimGrid. dimBlock ////  انجام میشود . سپس بلوک های نخ به صورت سریالی بین همهSM   هایی که بطور بالقوه دارای بیش از یک بلوک نخ در هر SM هسنتد، توزیع می شوند .

2-     بلوک های نخ متلق به هر SM به صورت تارهایی warp از نخ هاپس از فراخوانی کرنل توسط سخت افزار اغاز به کار میکند SM   تارهایی که اماده اجرا روی چند پردازنده هایی جریانی هستند را زمان بندی و اجرا می کند .

3-     محاسبات موازی توسط نخ ها در GPU انجام می شود

4-     کرنل پس از تکمیل محاسبات موازی به اتمام رسیده و تمام منابع ازاد میشود

سازمان دهی نخ های کودا در بلاک و گرید

بلوک یا بلاک .گروهی از نخ ها است (شکل 17) هماهنگی نخها با استفاده از تابع  syncthreads  انجام می شوند این تابع باعث توقف یک نخ در یک نقطه خاص در کرنل می شود تا زماینکه نخ های دیگر درون بلوک به همان نقطه برسند . تار warp   یک گروه متشکل از 32 نخ است که به صورت SIMD   توسط SM  اجرا میشوند . تارها واحدهای زمان بندی در یک SM   هستند . هر بلوک نخ به تارهای 32 نخی مستقیم می شود و نخ های درون تارها روی پردازندهای اسکالر اجراء می دشوند . برای مثال اگر 3 بلوک به یک SM  اختصاص داده شود و هر بلوک دارای 256 نخ باشد . تعداد تارها در هر بلوک برابر  8  (32/256) خواهد بود . این موضوع نشان میدهد که برای 3 بلوک 24 تار داریم . در هر لحظه تنهایی یکی از 24 تار برای واکشی و اجرای دستور العمل می شوند . زمانبندی تارها به صورت زیر انجام میگردد.

1-سخت افزار SM  زمابندی تار با سربار صفر را پیاده سازی میکند

2-تارهایی که عملوندهای دستور العمل بعدی را دراختیار دارند دارای اولویت برای اجرا هستند .

3-تارهای واجد شرایط براساس یک سیاست زمانبندی برای اجرا انتخاب میشود

4-همه ی نخ های درون یک تار در صورت انتخاب شدن .دستور العمل یکسانی را اجرا می کنند.

گروهی از بلوکها یک گرید را شکل می دهند . در کودا . کرنل یک گرید از بلوکهای نخ را اغاز می کند . این بلوک های نخ روی SM ها اجرا می شوند . نخ های درون هر بلوک روی پردازندهای اسکالر اجرا شده و میتوانند از طریق حافظه مشترک با هم همکاری کنند. نخ های درون بلوک های مختلف نمی تواند با هم همکاری داشته باشند .

شکل شماره 17:سلسله مراتب دو بعدی از بلوکها و نخها در پردازش یک تصویر 48*32 پیکسلی با استفاده از یک نخ در هر پیکسل

شکل 17و18  رابطه میان مولفه های مختلف کودا را نشان می دهد . می توان دید که معماری گرافیکی برای موازی سازی است . GPU پردازشگری است که به دلیل استفاده از معماری موازی و استفاده از تعداد هسته های زیاد امکان پردازش کاراتری را نسبت به CPU فراهم می کند ودر عین حال هزینه پایینی دارد . همچنین کودا .امکان استفاده از این معماری را به کاربر می دهد .

شکل 19: سلسه مراتب سازمان دهی نخ های اجرایی در کودا

 

برای دسترسی به یک نخ خاص متغیرهای درونی ویژه ای وجود دارد که در زبانهای برنامه نویسی حامی CUDA تعبیه شده اند . همچنین متغیرهای درونی ویزه ای نیز برای پی  بردن به ابعاد تور و بلوک های ایجاد شده در نظر گرفته شده است . این متغییرها نیز عمدتا سه بعدی هستند .

در معماری CUDA  حافظه ی کارت گرافیکی به خاطر افزایش کارایی دارای سلسه مراتب به شرح زیر است

1* st place : Register file

2*nd place : Shared Momory

3*rd place :Constant Memory

4*th : Texture Memory

*Tie for last place : Local Memory and Global Momory

Register ها : به ازای هر نخ یک register   وجود دارد که طول عمر داده های درون ان نیز به اندازه ی عمر نخ خواهد بود 

شکل 21:حافظه Register و نخ

Shared memory  به ازای هر بلوک از نخ ها یک قسمت حافظه ی مشترک وجود دارد که موجبات همکاری نخ های آان بلوک را فراهم می سازد . عمر داده های درون ان به اندازه عمر بلوک متناظر ان است و در ضمن زمان دسترسی به ان نیز کم است و لذا داده هایی که در ان قرار میگیرد در زمان سریعتری قالب دسترس اند

Local momory    به ازای هر نخ در قسمت DRAM  کارت گرافیکی یک قسمت حافظه ی محلی وجود دارد که عمر داده های درون ان به اندازه ی عمر نخ است

 

Global (device) memory   این حافظه یک حافظه عمومی است که هم توسط CPU و هم توسط تمامی نخ ها قابل دسترسی است . طول عمر داده ها درون ان هم از زمان Allocation   تا زمان  De allocation   است .ویژگی های این حافظه زمان تاخیر زیاد . ظرفیت بالا و عدم cache  شدن است

شکل24:حافظه Device

Host(CPU) memory    این حافظه به صورت مستقیم در دسترس نخ های device  نیست . بنابراین برای اینکه device    بتواند پردازشی روی ان ها انجام دهد باید داده ها درون ان ابتدا از حافظه های device انتقال یابد .

به ازای هر نخ یک حافظه local memory  ویک رجیستر وجو دارد و در سطح بعد . برای بلوک حافظه یک Shared momory ودر نهایت به ازای هر device یک حافظه ی global داریم . برای افزایش کارایی برنامه های  CUDA    سلسه مراتبی شامل تور. بلوک .و نخ در نظر گرفته شده است . حال زمان ان رسیده است تا بدانیم که اجرای این واحدها بر روی سخت افزار ها به چه صورت است . در شکل 25 با یک نمای ساده از ارتباطات سخت افزاری  CUDA اورده شده است .

در شکل 25   Global memory    حافظه عمومی device است و SMEM   خلاصه شده ی عبارت Shared memory  است . هر بلوک از تور بر روی یک multiprocessor  اجرا می شود " البته این امکان وجود دارد که چند بلوک روی یک multiprocessor اجرا شوند که این امر وابسته به منابع موجود در هر multiprocessor ونیز به ابعاد بلوک ها ست و درضمن اجرای یک نخ از یک multiprocessor وبه یک multiprocessor دیگر انتقال نخواهد یافت .

شکل26:سیستم معماری در موازی سازی CUDA

شکل 28 : حافظه اشتراکی در کودا

نخ های هر بلوک بسیار سبک اند و نیز به سادگی از حالت اجرا به حالت انتظار می روند و بلعکس . در واقع ایده ی اصلی برای این کاردر معماری CUDA   این است که برای تعویض حالت اجرای یک نخ نیازی به ذخیره سازی اطلاعات PCB نیست . PCB  بلوکی است که سیستم عامل اطلاعات هر پردازش رادر ان ذخیره می کند تا وضیعت اجرایی Process  حفظ شود .پس از انکه مجددا نوبت اجرا به این پردازش رسید . اطلاعات PCB  ان وضعیت رجیسترها  و ..را بازیابی کنند . هر multiprocessor به تعداد حداکثر نخ های که میتواند اجرا کند register  دارد که یک محدودیت سخت افزاری است . مثلا فرض کنید که یک multiprocessor حداکثر توانایی اجرای 256 نخ را داشته باشد . دراینصورت 256 رجیستر نیز خواهد داشت . درهر لحظه یکی از این نخ ها اجرا می شود و سپس اجرای ان رها شده ونوبت به دیگری می رسد . منتها برای تعویض اجرا نیازی به ذخیره سازی حالت نخ در حال اجرا نیست . زیرا تعداد کافی رجیستر در اختیار داریم .پس باخیال راحت اجرای ان را رها کرده و به سراغ نخ بعدی می رویم . نکته دیگری که دررابطه وجود دارد این است که تعداد بلوک هایی که می توانند به صورت همزمان روی یک multiprocessor اجرا شوند. بستگی به میزان منابع multiprocessor و نیز ابعاد بلوک دارد . قسمت shared memory  در شکل 28 حاظه ی مربوط بین نخ های یک بلوک است که برای همکاری این نخ ها تعبیه شده است Global memory  نیز حافظه ای از DRAM   مربوط به device  است که پل ارتباطی host      و   device   به شمار میرود . مدیریت اجرا و شناسه دهی به نخ هایی که multiprocessorها اجرا میکنند با خود انهاست . واحدهای زمانبندی در multiprocessor ها wrap   نام دارند . هر wrap  شامل تعداد نخ است . درهر زمان واحد زمانبند یک wrap   را به حالت اجرا می برد . multiprocessor روی تمامی نخ هایwrapمذکور دستور واحدی را اجرا می کند . به عنوان مثال اگر فرض کنیم که مثال قبلی هر 32 wrap  نخ را در خود جای میدهد . انگاه هر بلوک ما 4=32/128wrap   خواهد داشت و multiprocessor در کل 2/4 یعنی 8 wrap   خواهد داشت. با گذر زمان هر بار . multiprocessor یک wrap  را برداشته و دستور مناسب را درمورد ان اجرا کرده و به سراغ wrap  بعدی می رود .

13-همکاری نخ ها

برای افزایش کار امدی کد در CUDA  . این امکان فراهم شده است که نخ ها بتوانند با یکدیگر همکاری داشته باشند . این همکاری می تواند در زمینه ی دسترسی به حافظه باشد که باعث کاهش پهنای باند مصرف شده در برنامه می شود و یا میتواند در زمینه ی اشتراک نتایج بدست امده باشد که در این حالت سربار محاسبه ی مجدد برای هر نخ کاهش می یابد .لازم به ذکر است که همکاری بین تمام نخها قابل پیاده سازی نیست . زیرا اگر تعداد نخ های برنامه زیاد باشد نه تنها کارایی را افزایش نمدهد بلکه سبب کاهش ان نیز خواهد شد . بنابراین همکاری نخ ها فقط در یک بلوک صورت میگیرد . در حقیقت دسته بندی نخ ها در قالب بلوک های مجزا در یک تور به همین منظور صورت گرفته است . در هر بلوک یک حافظه shared memory   وجود دارد که بین نخ های ان بلوک مشترک است و نخ های ان بلوک می توانند از داده های درون ان استفاده کرده یا نتایج محاسبات خود را در ان قراردهند . علاوه بر همکاری از طریق حافظه ی مشترک نخ های درون یک بلوک می توانند اجرایشان رانیز با هم synchronize  کنند.

تـــذکر : نخ های یک بلوک نمیتواند با نخ های بلوک های دیگر همکاری داشته باشند . مدیریت حافظه هر کدام از device  ها و host   حافظه مربوط به خود را دارند . مدیریت حافظه device  در کد host  صورت می گیرد . این مدیریت شامل گرفتن و پس دادن حافظه . کپی کردن به device  یا برداشتن اطلاعات از ان است .

Memory Allocation and Release

به کمک توابع زیر میتوان حافظه ای به اندازه مورد نیاز در قسمت  device  از global memory  گرفت یا ان را پس داد.

. cuda Malloc(void**Pointer . size _ t nbytes )

. cuda Memset (void**Pointer .int value . size _ t nbytes )

. cudaFree (void**Pointer)

توابع  cudaMalloc و cudaFree به ترتیب حافظه ی مورد نیاز را می گیرند و پس می دهند  cudaMemset هم داده ی مورد نظر را در حافظه ی گرفته شده قرار می دهد . به کمک تابع زیر می توان فضایی از حافظه host  یا device را در فضایی از حافظه ی host  یا device قرار دهد .

CudaMemcpy ( void *dest . void * source . size _t bytes . enum cudaMemcpyKind direction )

enum cudaMemcpyKind  میتواند یکی از مقادیر زیر را بگیرد .

·         Cuda Memcpy Host Device

·         Cuda Memcpy Device To Host

·         Cuda Memcpy Device Device

·         Host synchronization

·         همه اجراهای kernel  به صورت غیر همگام صورت میگیرد . بدین معنا که بلافاصله پس ا زاجرا و پیش از اینکه اجرای همه نخ ها به پایان برسد به CPU   باز می گردد . برای همگام کردن نخ های د رحال اجرا از تابع cudaThread synchronize   استفاده می شود . همچنین لازم به ذکر است که تابع CudaMemcpy به صورت همگام اجرا می شود . بدین معنا کپی اغاز نمی شود  تا همه ی فراخوانی های قبلی CUDA  کامل شود و نیز کنترل به CUP  باز نمی گردد تا اینکه کل عملیات کپی کردن صورت گیرد .

پردازنده برداری

یک پردازنده برداری ، یا آرایه پرداز ،رایانه خاصی است با چند پردازنده برای پردازش موازی تعداد زیادی از آرایه ها

آرایه پرداز در واقع یک واحد پردازش مرکزی (CPU) می باشد که یک مجموعه از دستورالعملهایی را اجرا می کند که روی آرایه تک بعدی از داده که همان بردار است ، عمل می کند. این درست در مقابل پردازنده های عددی قرار می گیرد که دستورالعملهای آن ، صرفا روی یک تکه داده عمل می کند ،اکثر پردازنده ها ،پردازنده های عددی می باشند. پردازنده های برداری در دهه 1970 مورد بحث واقع شدند و پایه و اساس اکثر ابررایانه های دهه 1980 تا 1990 را تشکیل دادند پیشرفتهای پردازنده های عددی ،مخصوصا ریزپردازنده ها ،باعث کاهش بکار گیری پردازنده های برداری سنتی در ابررایانه ها و همچنین کاهش استفاده از تکنیکهای پردازش برداری در پردازنده ها در اوایل دهه 1990 شد.امروزه اکثر پردازنده ها از یک معماری استفاده می کنند که در آن دستورالعملهایی برای پردازش برداری روی چندین تکه داده ، را بطور برجسته نشان می دهد.این دستورالعملها را SMID (تک دستور،چند داده)می شناسند.از مثالهای مشهود اینگونه دستورالعملها می توان به AltiVec,MMX,SSE اشاره نمود.نمونه هایی از استفاده از تکنیک پردازش برداری را می توان در کنسولهای بازی و شتاب دهنده های گرافیکی نیز پیدا کرد.در سال 2000 شرکتهای آی بی ام و سونی و توشیبا با همکاری هم موفق به تولید یک پردازنده سلولی (نوعی ریزپردازنده)شدند ،که این پردازنده سلولی شامل یک پردازنده عددی و هشت پردازنده برداری بود که در پلی استیشن 3 مورد استفاده قرار گرفت.

در برخی از طراحی های پردازنده ها ،دستورالعملهای چندگانه روی چندین تکه داده عمل می کنند که آنها را به عنوان MMID (چند دستوره،چند داده)می شناسیم.اینگونه طراحی ها مختص کاربردهای ویژه هستند و بطور عادی برای استفاده های متداول کاربرد ندارند

 

تاریخچه

پردازش برداری اولین بار در اوایل دهه ۱۹۶۰ توسط وستینگهاوس، در پروژه Solomon مورد توجه قرار گرفت و توسعه یافت. هدف این پروژه افزایش چشمگیر سرعت محاسبات ریاضی، با استفاده همزمان از تعداد زیادی عملیات ساده ریاضی، زیر نظر یک پردازنده بود. به این صورت که پردازنده در هر کلاک یک سیگنال مشترکی را برای تمامی واحدهای محاسبه و منطق (ALU)ها می‌فرستاد. در حالی که هر یک از این واحدهای محاسبه ورودی‌های مجزایی داشتند، یک کار مشترک روی آن ورودی‌ها انجام می‌دادند. این روش به ماشین‌های Solomon این قابلیت را می‌داد که یک الگوریتم را روی انبوهی از داده‌ها (که از آرایه‌ها تغذیه می‌شدند) اجرا نماید.
در سال ۱۹۶۲، وستینگهاوس پروژه را لغو کرد ولی دوباره در دانشگاه ایلینوی تحت عنوان ILLIAC IV از سر گرفته شد. در طراحی ابتدایی آن، این ماشین به ۲۵۶ واحد محاسبه و منطق مجهز بود، درحالی که وقتی در سال ۱۹۷۲ عرضه شد تنها ۶۴ واحد محاسبه و منطق داشت و فقط می‌توانست ۱۰۰ تا ۱۵۰ میلیون فلاپس کار کند. برای کار با اطلاعات فشرده و حجیم، مانند دینامیک محاسباتی سیالات، همین ماشین معیوب (به دلیل اینکه نتوانستند تعداد ۲۵۶ واحد محاسبه و منطق ایجاد کنند) سریعترین ماشین دنیا بود.
تکنیک پردازش برداری تقریبا در تمام طراحی‌های پردازنده‌های مدرن وجود دارند، اگرچه معمولا با نام SIMD می‌شناسند. در اینگونه پردازنده‌ها، واحد پردازش برداری در کنار واحد پردازنده عددی کار می‌کند و برنامه‌هایی با آن بخش کار می‌کنند که واقعا می‌دانند که این واحد حضور دارد.

 

 

جزئیات

بیشتر پردازنده‌ها دستورالعملی دارند که بیان می‌کند «A را با B جمع کن و درون C بریز». مقادیر A، B و C (حداقل در تئوری) به ندرت می‌تواند داخل دستورالعمل باشند (چه به صورت صریح، چه رجیستر). در واقع داده به ندرت به طور خام فرستاده می‌شود، بلکه به صورت اشاره‌ای به آدرس حافظه که داده داخل آن است، می‌باشد. رمزگشایی[۲] این نشانی و دریافت داده از حافظه[۳]، خود زمان بر است. با افزایش سرعت پردازنده‌ها، این تاخیر حافظه تبدیل به مانعی بزرگ برای عملکرد سریع پردازنده محسوب می‌شود.

خط لوله

برای کاهش زمان تاخیر، اغلب پردازنده‌های امروزی از فن خط لوله[۴] استفاده می‌کنند. در این تکنیک دستورالعمل‌ها از چندین بخش عبور می‌کند تا نوبتش برای اجرا فرارسد. اولین بخش، آدرس را خوانده و کدگشایی می‌کند، بخش بعد مقادیر آدرسها را از حافظه می‌گیرد و بعدی کار محاسبه و اجرا را انجام می‌دهد. در تکنیک خط لوله، رمز کار در این است که شروع کدگشایی دستورالعمل بعدی، باید حتی قبل از خروج دستورالعمل قبلی از پردازنده صورت گیرد؛ در نتیجه واحد کدگشایی آدرس همواره مشغول به کار می‌باشد. هر دستورالعمل برای اجرای کامل به همان زمان قبلی (بدون خط لوله) نیاز دارد. زمانی که آن را تاخیر می‌نامیم. ولی پردازنده با خط لوله می‌تواند دسته‌ای از دستورالعملها را خیلی سریعتر انجام دهد.

پردازنده برداری

پردازنده برداری یک قدم فراتر بر می‌دارد و بجای ایجاد خط لوله برای دستورالعمل‌ها، داده‌ها را نیز خط لوله می‌کند. دستورالعملهایی هستند که بجای اینکه بگویند «A را با B جمع کن»، می‌گوید «تمامی اعداد از اینجا تا آنجا را با تمامی اعداد از اینجا تا آنجا جمع کن». و بجای اینکه دستورالعمل‌ها را پشت سر هم رمزگشایی و داده‌های مربوط به آنها را از حافظه دریافت کند، یک دستورالعمل را از حافظه می‌خواند و با فرض اینکه می‌داند که آدرس بعدی یکی بیشتر از آدرس فعلی است، دستورالعمل بعدی را رمزگشایی می‌کند. این عمل صرفه جویی چشمگیری در زمان رمزگشایی می‌کند. برای نشان دادن اینکه چه تفاوتی می‌کند، فرض کنید می‌خواهیم دو آرایه ۱۰ تایی از اعداد را با هم جمع کنیم. در حالت عادی نیاز به یک حلقه داریم که هر بار یک زوج از این دو آرایه را انتخاب می‌کند و سپس آنها را با هم جمع می‌کند

execute this loop 10 times
   read the next instruction and decode it
   fetch this number
   fetch that number
   add them
   put the result here
 end loop


در حالی که در پردازش برداری اینگونه خواهد بود:

 read instruction and decode it
 fetch these 10 numbers
 fetch those 10 numbers
 add them
 put the results here


در پردازش برداری، اول اینکه فقط دو انتقال آدرس از پردازنده به حافظه داریم، همچنین در این حالت بجای اینکه ۱۰ مرتبه یک دستورالعمل را کدگشایی کند تنها یک بار این کار را انجام می‌دهد. همچنین کد مورد استفاده در پردازش برداری کوتاه‌تر است که این خود سبب کاهش حافظه مورد نیاز برای دستورالعمل‌های آن می‌باشد.


بررسی وابستگی بین این اعداد لازم نیست چون دستورالعمل برداری، چندین عمل غیر وابسته را معین می‌کند. این خود منطق کنترل را ساده می‌کند. مسئله زمانی جالب تر می‌شود که بتوان چند عمل را روی چند داده انجام داد. کد زیر را در نظر بگیرید که در آن می‌خواهیم دو گروه عدد را با هم جمع کنیم و سپس با گروه سوم ضرب کنیم. در این کد عمل دریافت دستورالعمل فقط یک بار انجام می‌شود (بر خلاف حالت عادی که ۲*۱۰=۲۰) و این دو عمل فقط در یک دستورالعمل انجام می‌پذیرد:

 

 read instruction and decode it
 fetch these 10 numbers
 fetch those 10 numbers
 fetch another 10 numbers
 add and multiply them
 put the results here


عملیات ریاضی بالا خیلی سریعتر انجام خواهند شد چرا که دیگر تاخیر در دریافت و کدگشایی دستورالعمل بعدی را نداریم (فقط یک دستورالعمل داریم).

جنبه منفی

باید توجه داشت که تمام مسائل را نمی‌توان با استفاده از این روش بهبود داد. پیاده سازی این دستورالعمل‌ها در پردازنده خود پیچیدگی زیادی را بر هسته پردازنده تحمیل می‌کند. این پیچیدگی‌ها معمولا سبب می‌شوند که دستورالعمل‌های دیگر دیرتر اجرا شوند. به عنوان مثال زمانی که بخواهیم فقط دو عدد تنها را با هم جمع کنیم. همچنین دستورالعمل‌های پیچیده سبب کندی قسمت رمزگشایی و پیچیدگی بیشتر آن خواهد شد، که این خود باعث کندی اجرای دستورات عادی می‌شود.

جنبه مثبت و کاربرد

در حقیقت، پردازش برداری برای انجام عملیات روی انبوهی از داده‌ها بهترین کارایی را دارند. برای همین است که این پردازنده‌ها اصولا در ابر رایانه‌ها استفاده می‌شوند. این ابر رایانه‌ها عموما برای پیشبینی وضعیت هوا و آزمایشگاه‌های فیزیک استفاده می‌شوند که انبوهی از داده را به چالش می‌گیرد.

 

مثال دنیای واقعی: دستورالعملهای برداری در معماری x86

کد زیر مثال حقیقی معماری x86 با دستورالعمل‌های برداری می‌باشد. در اینجا از دو آرایه اعداد اعشاری استفاده می‌کند.

//SSE simd function for vectorized multiplication of 2 arrays with single-precision floatingpoint numbers
//1st param pointer on source/destination array, 2nd param 2. source array, 3rd param number of floats per array
 void mul_asm(float* out, float* in, unsigned int leng)
 {    unsigned int count, rest;
 
      //compute if array is big enough for vector operation
      rest  = (leng*4)%16;
      count = (leng*4)-rest;
 
     // vectorized part; 4 floats per loop iteration
      if (count>0){      
      __asm __volatile__  (".intel_syntax noprefix\n\t"
      "loop:                 \n\t"
      "movups xmm0, [ebx+ecx] ;loads 4 floats in first register (xmm0)\n\t"
      "movups xmm1, [eax+ecx] ;loads 4 floats in second register (xmm1)\n\t"
      "mulps xmm0,xmm1       ;multiplies both vector registers\n\t"
      "movups [eax+ecx],xmm0 ;write back the result to memory\n\t"
      "sub ecx,16            ;increase address pointer by 4 floats\n\t"
      "jnz loop              \n\t"
      ".att_syntax prefix    \n\t"
        : : "a" (out), "b" (in), "c"(count), "d"(rest): "xmm0","xmm1");
      }
      
      // scalar part; 1 float per loop iteration
      if (rest!=0)
      {
       __asm __volatile__  (".intel_syntax noprefix\n\t"
      "add eax,ecx           \n\t"
      "add ebx,ecx           \n\t"
      
      "rest:                 \n\t"
      "movss xmm0, [ebx+edx]  ;load 1 float in first register (xmm0)\n\t"
      "movss xmm1, [eax+edx]  ;load 1 float in second register (xmm1)\n\t"
      "mulss xmm0,xmm1       ;multiplies both scalar parts of registers\n\t"
      "movss [eax+edx],xmm0  ;write back the result\n\t"
      "sub edx,4             \n\t"
      "jnz rest              \n\t"
      ".att_syntax prefix    \n\t"
        : : "a" (out), "b" (in), "c"(count), "d"(rest): "xmm0","xmm1");
      }
      return;
 }

 

 

اندازه حافظه گرافیکی

اندازه حافظه گرافیکی

مقدار حافظه گرافیکی .به طور مستقیم . به اندازه صفحه نمایش (که بر حسب اینچ است ) رزولشون تصویر ( که برحسب پیکسل است) و تنظیمات اجرای تصاویر مثل Anti Aliasing بستگی دارد.

پس یک صفحه نمایشی 17 اینچی با رزولوشنی برابر با 1280*1024 نیازی به داشتن کارت گرافیکی با حداکثر میزان حافظه گرافیکی نخواهد بود ویک کارت گرافیکی یک گیگابایتی کاملا مناسب است . اگر حافظه گرافیکی از ان میزانی که باید . کمتر باشد .پردازنده گرافیکی برای اینکه تصاویر پردازش شده را به این واحد ارجاع دهد تا برای صفحه نمایش صادر شود . به اجبار باید منتظر خالی شدن حافظه بماند تا داده های جدید را در ان ادرس دهی نماید و این دقیقا همان زمانی است که افت فریم صورت میگیرد . اگر رزولوشن صفحه نمایش یک کامپیوتر 768*1024پیکسل باشد و چون رنگی است به صورت سه کانال  RGB   نمایش داده میشود پس هر پیکسل ان داری 8*8*8 بیت است یعنی 24بیت . پس در صفحه نمایش این کامپیوتر باید تعداد 768*1024*24 بیت در هر ثانیه بروز رسانی شود که این کار توسط کارت گرافیک صورت میگیرد .

واژه هایی مانند Shared Memoryو Up ToوDedicated Memory به چه چیز اشاره دارد؟

آیا حافظه اصلی یا همان RAM   با کارت گرافیک ، به اشتراک گذاشته می شود یا خیر ؟ اگر کارت گرافیکی دارای حافظه گرافیکی یا همان  VRAM به آن اندازه که گفته شده نباشد . از واژه    Shared Memory        برای مشخصات ان استفاده می کنیم . فرض کنید که کارت گرافیکی تهیه می کنید و برروی ان نوشته است  Up To 2GB   و یا   2GB Shared   این به این معنی است که این کارت به طور خاص داری حافظه گرافیکی نمی باشد وبرای پردازش داده ها نیازمند به استفاده از حافظه اصلی سیستم است .و اعداد جلوی انها به معنی این است که چیپ های مورد استفاده در انها نهایتا امکان ادرس دهی 2 گیگابایت حافظه رادارند . این موضوع باعث میشود تا درمواقعی که سیستم نیازمند حافظه اصلی است (به دلیل پردازش سنگین ) دچارافت کارایی شود  Dedicated Memory   همان حافظه گرافیکی است . کارت های گرافیکی ای که داری   DM   باشند دیگر نیازی به بهره گیری از حافظه اصلی سیستم را ندارد و حافظه درون ساختی را دارا هستند که از حافظه اصلی سیستم که اشتراک شده نیز هست . بسیار سریعتر عمل کرده و بازدهی بالاتری دارد . پهنای باندی که این نوع از حافظه ها در اختیار کارت گرافیک قرار می دهد نیز کاملا واقعی است .یکی از معمولی ترین حافظه هایی که امروزه در کارت های گرافیکی مورد استفاده قرا می گیرد  با نام   GDDR5   شناخته می شود که برای انتقال داده ها از چهار مسیر بهره می برد.


شکل 3: Geforce GTX 960 G1 GAMING با معماری پیشرفته Maxwell

 
ادامه مطلب ...