جمعه 29 تیر 1397 | Friday 20 th of July 2018 صفحه اصلی گروه الکترونیکی کامپیوتر
5ـ2ـ اولین برنامه

از انجاکه می خواهیم CUDA C را  با مثال بیاموزیم ، اجازه دهید به اولین مثال از CUDA C  نگاهی بیاندازیم. بر طبق قوانین نوشته شده حاکم در برنامه نویسی کامپیوتر شروع به کار میکنیم .با بررسی نمونه"”Hello,world!شروع میکنیم

5_2_1 HELLO, WORLD!

 

در این مرحله ،بدون شک با خود می اندیشید که این پایان نامه یک پایان نامه تقلبی است. ایا این همان زبان Cاست؟ایا زبانCUDA C وجود دارد؟ پاسخ هر دو این سوالات مثبت می باشد .این پایان نامه یک حیله گری ماهرانه نیست. مثال “Hello,World!”به عنوان نمونه ای است ، در سطح پایه ای ، هیچ تفاوتی میان CUDA Cواستاندارد که شما با ان در گذشته کار کرده اید وجود ندارد.

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

 

ما بتدریج بر روی این مثال ساده کار خواهیم کرد. بیایید نگاهی به کاربرد GPU(به عنوان دستگاه) برای اجرای کد بیاندازیم.تابعی که در یک دستگاه  قابل اجراست معمولا  Kernel(هسته)نامیده میشود .

5ـ2ـ2ـ  فرمان هسته ای

حال ما مثال فوق را با تعدادی کد که به نظر جدیدتر از مثال برنامه “Hello,world!”  به نظرمیرسد کامل میکنیم.

A KERNEL CALL

این برنامه دو نکته اصلی در مثال “Hello,World!”   اولیه  به وجود می اورد

  یک تابع بدون پارامتر که ) Kernel(نامیده میشود   و با__global__  (توابع سراسری)توصیف می شود.

  فراخوانی تابع Kernel  ، که با<<<1و1>>>> ایجاد شده.

همانطور که در بخش قبل دیدیم ، کدها به طور پیش فرض در سیستم  توسط  کامپایلر C  استاندارد کامپایل میشدند . برای مثال GNU gccممکن است کد میزبان روی سیستم عامل لینوکس را کامپایل کند، درحالیکه میکروسافت ویژوال  cکد میزبان روی سیستم عامل ویندوز را کامپایل میکند. ابزارهای NVIDIA  به سادگی کدهای شما را در کامپلیلر میزبان تغذیه می کند و همه چیز همان رفتاری را دارند که در محیط بدون CUDAعمل می کنند.

اکنون ما مشاهده می کنیم که CUDA Cتوصیف کننده  ____ globalرا به استاندارد Cاضافه میکند.مکانیسم هشداردهنده به جای اینکه کامپایلریک تابع را روی سیستم میزبان کاپایل کند انرا برای اجرا روی دستگاه کامپایل میکند.در این مثال ساده ،nvccتابعkernel()را به کامپایلر می دهد. تا  از ان طریق کد دستگاه را بگیرد ودر اخر تابع  Main()را به کامپایلر میزبان می دهد همانند عملی که در مثال قبل انجام شد.

بنابراین،چه چیز در فراخوانی تابع kernel()  مبهم می باشد وچرا باید استاندارد Cرا با []و اعداد تغییر دهیم. حال شتاب زده نشوید زیرا این همانجایی است که جادو اتفاق می افتد.

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

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

 

وعده داده بودیم که بتوانیم پارامترها را به سمت هسته  سیستممان عبور دهیم ، وحال زمان ان است  وعده یمان را عملی سازیم. برای بالا بردن فهم مطلب به مثال کاربردی “Hello,World”  می پردازیم

                    

شما متوجه تعدادی خطوط جدید در اینجا شده اید. اما این تغییرات تنها دو مفهوم کلی را معرفی می کند :

   می توانیم پارامترها را به سمت هسته همانند هر تابع  در زبان Cارسال کنیم.

  برای ذخیره مقادیر بازگشتی به سیستم میزبان به تخصیص حافظه برای انجام هر کار مفیدی روی دستگاه نیازمندیم.

هیچ قانون ویژه ای در مورد پارامترهای ارسالی به سمت هسته وجود ندارد. علی رغم اینکه دستورالعمل []،به نظر یک فراخوانی هسته ای به حساب می اید و دقیقا مانند هر فراخوانی تابع در استاندارد Cعمل می کند.سیتم زمان اجرا مراقب هر گونه پیچیدگی مطرح شده توسط این حقیقت که پارامترها  نیازمند انتقال از سیستم میزبان به دستگاه هستند می باشند.

جالب تر این که که تخصیص منبع از حافظه توسط دستور cudaMalloc()  صورت می گیرد.این فراخوانی شباهت زیادی با فراخوانی در استاندارد زبان Cکه با دستور malloc()  بیان می شود دارد با این تفاوت که در  زمان اجرای CUDA  ،فرمان می دهد که تخصیص منبع از حافظه بر روی دستگاه صورت گیرد. اولین ارگومان(متغیر) اشاره گری است که اشاره دارد  به مکانی که ادرس حافظه تازه تخصیص یافته شده در ان نگه داشته شده ودومین پارامتر مربوط به سایز منبعی است که می خواهید بسازید. در نظر داشته باشید اشاره گر تخصیص حافظه مقداری را به تابع برنمی گرداند و این همان رفتاری است که  تابعmalloc()  نشان میدهد و مقدار void *را بر می گرداند.تابعHANDLE_ERROR()  که این فراخوانی ها را دربر گرفته   یک ماکرو سودمند می باشد که ما به عنوان کد پشتیبان در بخشی از این پایان نامه ارائه دادیم . این تابع بسادگی فراخوانی هایی که مقداربرگشتی انها خطا می باشد را کشف ، پیغام های خطا مربوطه را چاپ ، و توسط کدEXIT_FAILUREاز برنامه های کاربردی خارج می شود. اگرچه شما در استفاده از این کد در برنامه کاربردی خود ازاد هستید ، احتمال اینکه این کد کنترل خطا در کد به وجود امده درست عمل نکند ، بسیار وجود دارد.

این عامل باعث افزایش دقت می شود که البته نکته بسیار مهمی است.بخش عمده ای از سادگی و قدرت CUDA C  در توانایی محو کردن خطوط بین کد دستگاه و کد میزبان ناشی می شود ، واین مسئولیت برنامه نویس است و نه مکانی که اشاره گر بر می گرداند توسط  تابع cudaMalloc()از کد ی که روی سیستم میزبان در حال اجراست . کد میزبان ممکن است اشاره گرها را به بیرون ارسال کند ،محاسباتی روی انها انجام دهد، ویا حتی انرا در قالب نوع های دیگر در اورد.ولی  شما نمی توانیداز ان جهت خواندن ونوشتن از حافظه  استفاده کنید.

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

می توانید اشاره گرهای تخصیص داده شده را با استفاده از تابعcudaMalloc()  به توابعی که بر روی دستگاه در حال اجراست ارسال کنید.

می توانید از اشاره گرهای تخصیص داده شده  با استفاده از تابعcudaMalloc()  جهت خواندن ونوشتن ازحافظه از کدی که بر روی دستگاه در حال اجراست استفاده کنید.

می توانید اشاره گرهای تخصیص داده شده را با استفاده از تابع cudaMalloc()به توابعی که بر روی سیستم میزبان درحال اجرا هستند ارسال کنید.

نمی توان از اشاره کرهای تخصیص داده شده با استفاده از تابع cudaMalloc()جهت خواندن یا نوشتن ازحافظه از کدی که بر روی سیستم میزبان در حال اجراست استفاده کرد.

اگر شما بدقت مطالب را خوانده باشید ،ممکن است بحث بعدی را پیش بینی کنید:ما نمی توانیم از توابع استاندارد C(free()) برای واگذاری حافظه استفاده کنیم ،ما حافظه را  با استفاده از تابعcudaMalloc() تخصیص می دهیم برای ازادسازی حافظه ای که با استفاده از cudaMalloc()انرا تخصیص داده ایم نیازمدیم تابع cudaFree() را فراخوانی کنیم، و این تابع دقیقا همان رفتاری را نشان می دهد که تابع free()از خود نشان می دهد.

ما شاهد این هستیم که جگونه سیستم میزبان به عنوان حافظه ازاد وتخصیص یافته بر روی دستگاه کاربرد دارد ، اما متاسفانه به وضوح می دانیم که نمی توان مکان این حافظه را از روی سیستم میزبان تغییر داد. دو خط باقیمانده از برنامه نمونه دو تا از متداول ترین روش ها برای دسترسی به حافظه دستگاه را نشان می دهد- که یکی استفاده از اشاره گرهای دستگاه از کد درون دستگاه و دیگری با فراخوانی تابعcudaMemcpy() .

از اشاره گرهای که  در کد درون دستگاه قرار دارند دقیقا به همان صورتی استفاده می شوند که از انها در استانداردC  به منظور اجرا روی کد میزبان استفاده می شود . دستورa+b=*cخیلی ساده به نظر می رسددو پارامتر a و bرا با یکدیگر جمع می کندو نتیجه را در نقطه ای از حافظه به نام cذخیره می کند. ما امیدواریم به نسبت جالب بودنش بیش از حد اسان باشد.

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

همانطور که وعده داده شد، می توانیم در یک دستگاه توسط فراخوانی تابع cudaMemcpy()از طریق کد سیستم میزبان به حافظه دسترسی پیدا کرد. این فرخوانی دقیقا همانند تابع memcpy()در استاندارد cرفتار می کند به همراه یک پارامتر اضافی که برای تعیین مبدا و مقصد نقطه اشاره گر در حافظه دستگاه به کار می رود. در این مثال متوجه می شوید که پارامتر اخر به تابعcudaMemcpy()  به صورتcudaMemcpyDevice To Hostمی باشد، و در حین اجرا راهنمایی می کند که اشاره گر مبدا اشاره گر دستگاه است واشاره گر مقصد یک اشاره گر سیستم میزبان می باشد

جای تعجب نیست که،cudaMemcpyHostTo Deviceعکس این حالت را نشان می دهد، جائیکه مبدا داده ها  بر روی سیستم میزبان قراردارد ومقصد ادرسی است که بر روی دستگاه می باشد . در نهایت حتی می توان هر دو اشاره گر را بر روی دستگاه با ارسالcudaMemcpy Device To Device  تعیین کرد.اگر اشاره گر مبدا و مقصد هر دو بر روی سیستم میزبان باشند ،می توانیم به طور معمول برای کپی کردن بین انها به سادگی ار تابع memcpy()استاندارد cاستفاده کنیم

Compatability by:
آخرین به روز رسانی سایت: سه شنبه, 22 اسفند 1391 - 00:26