UfnCod3r
چهارشنبه 06 شهریور 1392, 14:23 عصر
:لبخند:
سلام
در این تاپیک قصد دارم تا چیزایی که خودم از SIMD, SSE می دونم رو بطور ساده توضیح بدم بقیه
هم راحت یاد بگیرن و بیفتن روی چرخ :چشمک::شیطان:
اول از همه بهتره اینا رو بخونید:گیج:
http://en.wikipedia.org/wiki/SIMD
http://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
-
-
SIMD مخفف Single instruction, multiple data هست . یعنی یک دستور چند داده
کارش از اسمش معلومه
ینی ما یک دستورالعمل رو مشخص کنیم و اون رو روی چند تا داده اعمال کنیم .
این رو در نظر بگیرد .
float a[4] = {1,1,1,1};
float b[4] = {2,2,2,2};
float c[4];
c[0] = a[0] + b[0];
c[1] = a[1] + b[1];
c[2] = a[2] + b[2];
c[3] = a[3] + b[3];
این کدمون الان حالت برداری داره .
دستورات هر خط یکی هستن فقط داده ها تغییر می کنن.
تو این کد ما 4 تا عدد اعشاری رو با 4 تای دیگه جمع کردیم و ذخیره کردیم .
در مجموع میشه 4 تا عمل جمع و 12 تا عمل خواندن و نوشتن .
خب اگه میشد به این پردازشگر بفهمونیم که از a[0 تا a[3 رو با b[0 تا b[3 جمع کن خیلی خوب میشد .:متفکر:
بله میشه .
به مرور زمان که پردازشگر ها سریع تر شدن ، چند هسته ای شدن ،کش امد و ی سری چیزه دیگه
ی سری دستور عمل ها هم اضافه شدن . درواقع ی هردفعه الحاقی های جدید میاد
یکی از اون ها SSE هست .
با امدن SSE هشت تا ثبات 128 بیتی امد به نام XMM0...XMM7
تو این ثبات ها میشه 4 تا float رو جا داد و ضرب و جمع و دستورای دیگه رو اعمال کرد . چیزای دیگم میشه.
بعد هردفعه نسخه های جدید تر از SSE امد و چیز هایی دیگه مثل اعداد صحیح و
دستورالعمل های جدید اضافه شد .
برای اینکه بفهمید CPU ی شما چه الحاقی هایی رو پشتیبانی می کنه می تونید از برنامه CPUZ استفاده کنید
درحال حاضر میشه گفت اشغال ترین پی سی های امروزی SSE1,2,3 رو پشتیبانی می کنن . پس از این بابت نگران نباشید.
خب برای استفاده از این دستورات ما چند تا راه داریم .
-از اسمبل استفاده کنیم (خریت)
اسمبل نوشتن اصلا گزینه خوبی نیست . خوانایی کد کم میشه. کلی بهمون فشار میاد
برا کامپایلر ها و معماری های مختلف باید کد متفاوت بزنید و کلی دردسر دیگه
- به کامپایلر بگیم خودش کد رو تولید کنه (خریت)
کامپایلر ها اونقدر ها هم زرنگ نیستن .
هرچی کار کنن نمی تونن کد موازی تولید کنن . تو این زمینه نمیشه کاریشون کرد.
راه اخرش اینه که از ی سری توایع از پیش تعریف شده استفاده کنیم.
این توابع در واقع هر کدومشون یک دستور هستن (بجز بعضی ها) که خود کامپایلر بر اساس اونا کد اسمبل رو تولید می کنه .
این طوری نه عذاب اسمبل نوشتن رو می کشیم و هم اینکه برای کامپایلر ها و پلتفرم های مختلف لازم نیست کد رو دست بزنیم
این توابع تو این سرفایل ها قرار دارن. نمی دونم چرا اینطوری نام گزاری کردن:متفکر:
SSE xmmintrin.h
SSE2 emmintrin.h
SSE3 pmmintrin.h
Suppl. SSE3 tmmintrin.h
SSE4.1 smmintrin.h
SSE4.2 nmmintrin.h
AES, PCLMUL wmmintrin.h
AVX immintrin.h
خب حالا همون مثال بالا رو با SSE می نویسیم
float a[4] = {1,1,1,1};
float b[4] = {2,2,2,2};
float c[4];
__m128 xmm0 = _mm_loadu_ps(a);
__m128 xmm1 = _mm_loadu_ps(b);
__m128 xmm2 = _mm_add_ps(xmm0, xmm1);
_mm_storeu_ps(c, xmm2);
الان تو این کد دیگه کلا 4 تا دستوربیشتر انجام نمیشه . MOVUPS, MOVUPS, MOVUPS, ADDPS
در مورد نام تابع ها اگه دقت کنید قلقش میاد دستتون
اینایی که مربوط به SSE هستن همه با _mm_ شروع می شن.
و در اخر هم با ps_ یا ss_ تموم میشن . چیزایی دیگم هست که خودتون باید بفهمید
ps از
packed single گرفته شده
منظور از packed اینه که این عمل روی چهار تا عنصر این ثبات اعمال میشه
single هم منظورش single precision floating point . یا همون اعدادد ممیز شناور تک دقت هست. float خودمون.:لبخندساده::گیج:
ss از single single گرفته شده .
منظور از single اول اینه که این عمل فقط روی 32 بیته اول یا همون عنصر اول اعمال میشه و 3 تای دیگه رو کاری نداره . که درواقع کدمون از حالت موازی خارج میشه .
یکی از مشکلات کدی که کامپایلر تولید می کنه همینه . نمی تونه کد موازی تولید کنه و همش تک تک کار میکنه
single اخرهم به معنای همون single precision floating point هست که گفتم.
بین این ها هم عملکرد تابع رو مشخص میکنه .
پس خیل راحت میشه کار
__m128 _mm_add_ps(__m128 a , __m128 b );
رو فهمید.( ن پ ن بیام بگم add یعنی جمع کردن :عصبانی++:)
اول بهتره m128__ رو توضیح بدم . این قضیش مثل همین توابع هست .
این ی ساختار هست مثل همه ی ساختار های دیگه.
کامپایلر خودش این رو با ثبات های XMM0..XMM7 جایگزین می کنه .
درصورت کمبودن ثبات هم از استک استفاده می کنه .
بهتره این رو فقط داخل توابع استفاده کنید نه به عنوان عضو کلاس یا امثالش.
برای لود کردن داده ها داخل ثبات و برعکس هم باید از
load, loadu , store, storeu استفاده بشه .
u در اینجا به معنی UnAlignment هست
وفتی از _mm_load_ps و _mm_store_ps استفاده میشه Data-align ادرسی که می دیم حتما باید 16 باشه .
ینی ادرسش بایدضریبی از 16 باشه . این حالت خیلی سریع تره.
دلیلش هم برمیگرده به معماری سیستم و انتقال اطلاعات از رم به سی پی یو و برعکس .
این data-alignment هم دنیای هست واس خودش . :لبخند:
ولی با _mm_stoeu_ps, _mm_loadu_ps دیگه کاری به این کارا نداره ولی سرعتش کمتره .
خب حالا می خوایمکد رو تغییر بدیم تا سریع عمل کنه
#define MS_ALIGN16 __declspec(align(16))
MS_ALIGN16 float a[4] = {1,1,1,1};
MS_ALIGN16 float b[4] = {2,2,2,2};
MS_ALIGN16 float c[4];
__m128 xmm0 = _mm_load_ps(a);
__m128 xmm1 = _mm_load_ps(b);
__m128 xmm2 = _mm_add_ps(xmm0, xmm1);
_mm_store_ps(c, xmm2);
با صفتی که به متغیر ها دادیم (__declspec(align(16))) الان اونا در جایی قرار می گیرن که ادرسشون ضریبی از 16 باشه.
البته این برا MSVC کار میکنه برا کامپایلر های دیگه چیز دیگه ای باید بنویسید.
...
تامن بقیش رو می نویسم شما تشکر این ارسال رو بزنید:عصبانی::قلب:
سلام
در این تاپیک قصد دارم تا چیزایی که خودم از SIMD, SSE می دونم رو بطور ساده توضیح بدم بقیه
هم راحت یاد بگیرن و بیفتن روی چرخ :چشمک::شیطان:
اول از همه بهتره اینا رو بخونید:گیج:
http://en.wikipedia.org/wiki/SIMD
http://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
-
-
SIMD مخفف Single instruction, multiple data هست . یعنی یک دستور چند داده
کارش از اسمش معلومه
ینی ما یک دستورالعمل رو مشخص کنیم و اون رو روی چند تا داده اعمال کنیم .
این رو در نظر بگیرد .
float a[4] = {1,1,1,1};
float b[4] = {2,2,2,2};
float c[4];
c[0] = a[0] + b[0];
c[1] = a[1] + b[1];
c[2] = a[2] + b[2];
c[3] = a[3] + b[3];
این کدمون الان حالت برداری داره .
دستورات هر خط یکی هستن فقط داده ها تغییر می کنن.
تو این کد ما 4 تا عدد اعشاری رو با 4 تای دیگه جمع کردیم و ذخیره کردیم .
در مجموع میشه 4 تا عمل جمع و 12 تا عمل خواندن و نوشتن .
خب اگه میشد به این پردازشگر بفهمونیم که از a[0 تا a[3 رو با b[0 تا b[3 جمع کن خیلی خوب میشد .:متفکر:
بله میشه .
به مرور زمان که پردازشگر ها سریع تر شدن ، چند هسته ای شدن ،کش امد و ی سری چیزه دیگه
ی سری دستور عمل ها هم اضافه شدن . درواقع ی هردفعه الحاقی های جدید میاد
یکی از اون ها SSE هست .
با امدن SSE هشت تا ثبات 128 بیتی امد به نام XMM0...XMM7
تو این ثبات ها میشه 4 تا float رو جا داد و ضرب و جمع و دستورای دیگه رو اعمال کرد . چیزای دیگم میشه.
بعد هردفعه نسخه های جدید تر از SSE امد و چیز هایی دیگه مثل اعداد صحیح و
دستورالعمل های جدید اضافه شد .
برای اینکه بفهمید CPU ی شما چه الحاقی هایی رو پشتیبانی می کنه می تونید از برنامه CPUZ استفاده کنید
درحال حاضر میشه گفت اشغال ترین پی سی های امروزی SSE1,2,3 رو پشتیبانی می کنن . پس از این بابت نگران نباشید.
خب برای استفاده از این دستورات ما چند تا راه داریم .
-از اسمبل استفاده کنیم (خریت)
اسمبل نوشتن اصلا گزینه خوبی نیست . خوانایی کد کم میشه. کلی بهمون فشار میاد
برا کامپایلر ها و معماری های مختلف باید کد متفاوت بزنید و کلی دردسر دیگه
- به کامپایلر بگیم خودش کد رو تولید کنه (خریت)
کامپایلر ها اونقدر ها هم زرنگ نیستن .
هرچی کار کنن نمی تونن کد موازی تولید کنن . تو این زمینه نمیشه کاریشون کرد.
راه اخرش اینه که از ی سری توایع از پیش تعریف شده استفاده کنیم.
این توابع در واقع هر کدومشون یک دستور هستن (بجز بعضی ها) که خود کامپایلر بر اساس اونا کد اسمبل رو تولید می کنه .
این طوری نه عذاب اسمبل نوشتن رو می کشیم و هم اینکه برای کامپایلر ها و پلتفرم های مختلف لازم نیست کد رو دست بزنیم
این توابع تو این سرفایل ها قرار دارن. نمی دونم چرا اینطوری نام گزاری کردن:متفکر:
SSE xmmintrin.h
SSE2 emmintrin.h
SSE3 pmmintrin.h
Suppl. SSE3 tmmintrin.h
SSE4.1 smmintrin.h
SSE4.2 nmmintrin.h
AES, PCLMUL wmmintrin.h
AVX immintrin.h
خب حالا همون مثال بالا رو با SSE می نویسیم
float a[4] = {1,1,1,1};
float b[4] = {2,2,2,2};
float c[4];
__m128 xmm0 = _mm_loadu_ps(a);
__m128 xmm1 = _mm_loadu_ps(b);
__m128 xmm2 = _mm_add_ps(xmm0, xmm1);
_mm_storeu_ps(c, xmm2);
الان تو این کد دیگه کلا 4 تا دستوربیشتر انجام نمیشه . MOVUPS, MOVUPS, MOVUPS, ADDPS
در مورد نام تابع ها اگه دقت کنید قلقش میاد دستتون
اینایی که مربوط به SSE هستن همه با _mm_ شروع می شن.
و در اخر هم با ps_ یا ss_ تموم میشن . چیزایی دیگم هست که خودتون باید بفهمید
ps از
packed single گرفته شده
منظور از packed اینه که این عمل روی چهار تا عنصر این ثبات اعمال میشه
single هم منظورش single precision floating point . یا همون اعدادد ممیز شناور تک دقت هست. float خودمون.:لبخندساده::گیج:
ss از single single گرفته شده .
منظور از single اول اینه که این عمل فقط روی 32 بیته اول یا همون عنصر اول اعمال میشه و 3 تای دیگه رو کاری نداره . که درواقع کدمون از حالت موازی خارج میشه .
یکی از مشکلات کدی که کامپایلر تولید می کنه همینه . نمی تونه کد موازی تولید کنه و همش تک تک کار میکنه
single اخرهم به معنای همون single precision floating point هست که گفتم.
بین این ها هم عملکرد تابع رو مشخص میکنه .
پس خیل راحت میشه کار
__m128 _mm_add_ps(__m128 a , __m128 b );
رو فهمید.( ن پ ن بیام بگم add یعنی جمع کردن :عصبانی++:)
اول بهتره m128__ رو توضیح بدم . این قضیش مثل همین توابع هست .
این ی ساختار هست مثل همه ی ساختار های دیگه.
کامپایلر خودش این رو با ثبات های XMM0..XMM7 جایگزین می کنه .
درصورت کمبودن ثبات هم از استک استفاده می کنه .
بهتره این رو فقط داخل توابع استفاده کنید نه به عنوان عضو کلاس یا امثالش.
برای لود کردن داده ها داخل ثبات و برعکس هم باید از
load, loadu , store, storeu استفاده بشه .
u در اینجا به معنی UnAlignment هست
وفتی از _mm_load_ps و _mm_store_ps استفاده میشه Data-align ادرسی که می دیم حتما باید 16 باشه .
ینی ادرسش بایدضریبی از 16 باشه . این حالت خیلی سریع تره.
دلیلش هم برمیگرده به معماری سیستم و انتقال اطلاعات از رم به سی پی یو و برعکس .
این data-alignment هم دنیای هست واس خودش . :لبخند:
ولی با _mm_stoeu_ps, _mm_loadu_ps دیگه کاری به این کارا نداره ولی سرعتش کمتره .
خب حالا می خوایمکد رو تغییر بدیم تا سریع عمل کنه
#define MS_ALIGN16 __declspec(align(16))
MS_ALIGN16 float a[4] = {1,1,1,1};
MS_ALIGN16 float b[4] = {2,2,2,2};
MS_ALIGN16 float c[4];
__m128 xmm0 = _mm_load_ps(a);
__m128 xmm1 = _mm_load_ps(b);
__m128 xmm2 = _mm_add_ps(xmm0, xmm1);
_mm_store_ps(c, xmm2);
با صفتی که به متغیر ها دادیم (__declspec(align(16))) الان اونا در جایی قرار می گیرن که ادرسشون ضریبی از 16 باشه.
البته این برا MSVC کار میکنه برا کامپایلر های دیگه چیز دیگه ای باید بنویسید.
...
تامن بقیش رو می نویسم شما تشکر این ارسال رو بزنید:عصبانی::قلب: