View Full Version : حرفه ای: مقایسه توابع SSE با غیر SSE
God of War 2
یک شنبه 22 اردیبهشت 1392, 01:15 صبح
سلام.
بنده چند وقتی هست که در حال پیاده سازی کلاسهای برداری بر پایه SSE هستم.
یه مشکلی برام پیش امده و برام مبهمه.(حتی در فروم های انگلیسی زبان هم به جواب نرسیدم!)
من 3 نوع پیاده سازی آزمایش برای اپراتور =+ برای کلاس بردار 2 بعدی از نوع double نوشتم که 2 تاش بر پایه SSE و دیگری معمولی. چیزی که برام عجیبه اینه که دقیقا برعکس چیزی که انتظار میره عمل میکنند! یعنی در بهترین شرایط از نظر سرعت به شکل زیر عمل میکنند:
sse impl1 < sse impl2 <= non-sse
پیاده سازی ها:
// non-sse
template <typename T> // Double
static inline KVector2<T>& operator +=(KVector2<T>& left, const KVector2<T>& right){
left.x += right.x;
left.y += right.y;
return left;
}
// SSE Impl 2
static inline KSSEVector2d& operator +=(KSSEVector2d& left, const KSSEVector2d& right){
_mm_store_pd(&left.component[KVC_X],
_mm_add_pd(_mm_load_pd(left.component), _mm_load_pd(right.component)));
return left;
}
// SSE Impl 1
static inline KSSEVector2d& operator +=(KSSEVector2d& left, const KSSEVector2d& right){
asm volatile("movapd %0, %%xmm0"::"m"(*left.component));
asm volatile("movapd %0, %%xmm1"::"m"(*right.component));
asm volatile("addpd %%xmm0, %%xmm1":);
asm volatile("movapd %%xmm1,%0":"=m"(*left.component));
return left;
}
من کدهای اسمبلی رو مقایسه و تصحیی کردم و کدهای هرتابع به این شکل هست:
SSE Impl 2
0x4017cc <+0x0000> push %ebp
0x4017cd <+0x0000> mov %esp,%ebp
0x4017cf <+0x0000> sub $0x58,%esp
0x4017d2 <+0x0000> mov 0xc(%ebp),%eax
0x4017d5 <+0x0000> mov %eax,-0xc(%ebp)
0x4017d8 <+0x0000> mov -0xc(%ebp),%eax
0x4017db <+0x0000> movapd (%eax),%xmm0
0x4017df <+0x0000> mov 0x8(%ebp),%eax
0x4017e2 <+0x0000> mov %eax,-0x10(%ebp)
0x4017e5 <+0x0000> mov -0x10(%ebp),%eax
0x4017e8 <+0x0000> movapd (%eax),%xmm1
0x4017ec <+0x0000> movapd %xmm1,-0x28(%ebp)
0x4017f1 <+0x0000> movapd %xmm0,-0x38(%ebp)
0x4017f6 <+0x0000> movapd -0x38(%ebp),%xmm0
0x4017fb <+0x0000> movapd -0x28(%ebp),%xmm1
0x401800 <+0x0000> addpd %xmm1,%xmm0
0x401804 <+0x0000> mov 0x8(%ebp),%eax
0x401807 <+0x0000> mov %eax,-0x3c(%ebp)
0x40180a <+0x0000> movapd %xmm0,-0x58(%ebp)
0x40180f <+0x0000> mov -0x3c(%ebp),%eax
0x401812 <+0x0000> movapd -0x58(%ebp),%xmm0
0x401817 <+0x0000> movapd %xmm0,(%eax)
0x40181b <+0x0000> mov 0x8(%ebp),%eax
0x40181e <+0x0000> leave
0x40181f <+0x0000> ret
SSE Impl 1
0x4017cc <+0x0000> push %ebp
0x4017cd <+0x0000> mov %esp,%ebp
0x4017cf <+0x0000> mov 0x8(%ebp),%eax
0x4017d2 <+0x0000> movapd (%eax),%xmm0
0x4017d6 <+0x0000> mov 0xc(%ebp),%eax
0x4017d9 <+0x0000> movapd (%eax),%xmm1
0x4017dd <+0x0000> addpd %xmm0,%xmm1
0x4017e1 <+0x0000> mov 0x8(%ebp),%eax
0x4017e4 <+0x0000> movapd %xmm1,(%eax)
0x4017e8 <+0x0000> mov 0x8(%ebp),%eax
0x4017eb <+0x0000> pop %ebp
0x4017ec <+0x0000> ret
پیاده سازی دوم نمونه اصلاح شده هست و من مستقیما از توابع inline assembly استفاده کردم که انتظار میره سریعتر باشه اما اینطور نیست!(تقریبا 3x کندتر هست - تست شامل 4 حلقه با شمارشگر 1e5 بوده - تمام متغییر های 128 بیتی کاملا aligned هستند)
من کدهارو با GCC 4.6 & 4.7 با تنظیمات:
msse1 msse2 O3
کامپایل کردم که البته من فقط به SSE2 نیاز دارم .و CPU مورد استفاده هم Intel Corei7 هست.
کسی فهمیده مشکل دقیقا کجاست؟
UfnCod3r
یک شنبه 22 اردیبهشت 1392, 08:26 صبح
اتفاقا منم اینو نوشتم ی نگا بنداز روحیه بگیرم :لبخند:
http://barnamenevis.org/showthread.php?397792-***Vector3-SSE***
اول از همه فکر نکنم اصلا بردار دو بعدی double نیاز باشه .:متفکر: اونم برا موتور دو بعدی :متفکر:
بهتره اسمبل نزنی (حوصله داری ها ) من خودم اولا با اسمبل می نوشتم کلی درد سر داشت . هم خوانایی کد کم میشه هم تو کامپایلر های دیگه
باید باز دوباره کد بزنی برا 64 بیتی دوباره باز باید تغییر بدی خلاصه سرویس می شی :لبخند:
الان از VC استفاده می کنم کد اسمبلی که تولید می کنه دقیقا همون چیزیه ک ادم انتظار داره . :تشویق:
من کدهارو با GCC 4.6 & 4.7 با تنظیمات:
msse1 msse2 O3
اینا معادل همون Arch:SSE, Arch:SSE2 تو VC هست؟
اگه کامپایلر رو تنظیم کنی ک خودش SSE تولید کنه افتصاه میشه . من روی recast و چند تا برنامه دیگه تنظیم کردم سریع تر ک نشد هیچ کند ترم شد .:قهقهه: کلا از FPU استفاده نمی کنه
و خیلی از shuffle**, ***_ss استفاده می کنه . فکر کنم واس همینم کند تر میشه . :متفکر:
البته اینا ک گفتم برا VC بود:لبخندساده:
FastCode
یک شنبه 22 اردیبهشت 1392, 08:41 صبح
فکر میکنم gcc مشکلش چیز دیگست.
اون کدی که بدون SSE نوشتید از AVX استفاده میکنه که سریعتره.
میتونید با gcc 4.8 هم آزمایش کنید؟خیلی تغییر کرده.
اسمبلی non-sse رو هم بزارید
UfnCod3r
یک شنبه 22 اردیبهشت 1392, 09:39 صبح
من این رو امتحان کردم
__declspec(align(16)) struct Vec2
{
double xy[2];
Vec2& operator += (const Vec2& v);
};
Vec2& Vec2::operator+=( const Vec2& v )
{
_mm_store_pd(this->xy, _mm_add_pd(_mm_load_pd(this->xy), _mm_load_pd(v.xy)));
return *this;
}
اینم اسمبلش
PUBLIC ??YVec2@@QAEAAU0@ABU0@@Z ; Vec2::operator+=
_TEXT SEGMENT
??YVec2@@QAEAAU0@ABU0@@Z PROC ; Vec2::operator+=, COMDAT
; _this$ = eax
; _v$ = ecx
; 12 : _mm_store_pd(this->xy, _mm_add_pd(_mm_load_pd(this->xy), _mm_load_pd(v.xy)));
movapd xmm0, XMMWORD PTR [ecx]
addpd xmm0, XMMWORD PTR [eax]
movapd XMMWORD PTR [eax], xmm0
; 13 : return *this;
; 14 : }
ret 0
??YVec2@@QAEAAU0@ABU0@@Z ENDP ; Vec2::operator+=
خدایی ازین بهتر می شه
کلا 3 تا دستور بیشتر نشده حالا حی بگید VC بده
FastCode
یک شنبه 22 اردیبهشت 1392, 10:00 صبح
کلا 3 تا دستور بیشتر نشده حالا حی بگید VC بده
VC بده.:P
انتظار داری کسی که سه سال اخیر رو با gcc کار کرده بگه VC خوبه؟
اگر به اسمبلی نوشتنه که LLVM از این هم سریعتره چون در صورت امکان خودش تبدیل به AVX یا هر instruction ی که بعدش بیاد میکنه.بدون نیاز به کامپایل مجدد.
UfnCod3r
یک شنبه 22 اردیبهشت 1392, 10:28 صبح
اون کدی که بدون SSE نوشتید از AVX استفاده میکنه که سریعتره.
:متعجب:
AVX اسم ثبات هاش YMM هست دستوراشم همه با V شروع میشه من ک تو اون کد چنین چیزی ندیدم :متفکر:
http://en.wikipedia.org/wiki/Advanced_Vector_Extensions
FastCode
یک شنبه 22 اردیبهشت 1392, 10:44 صبح
:متعجب:
AVX اسم ثبات هاش YMM هست دستوراشم همه با V شروع میشه من ک تو اون کد چنین چیزی ندیدم :متفکر:
http://en.wikipedia.org/wiki/Advanced_Vector_Extensions
من هم اینها رو میدونم.اگر دقت کنید گفتم بدون SSE رو خود کامپایلر ممکنه به AVX تغییر بده.و چون DisAsm ه non-sse رو قرار نداده بودن گفتم اون رو هم بزارن که شکم برطرف بشه.
God of War 2
یک شنبه 22 اردیبهشت 1392, 11:56 صبح
non-sse disasm
0x401b96 <+0x0000> ite::KVector2<double>&, Kite::KVector2<double> const&)+20>: mov 0x8(%ebp),%eax
0x401b99 <+0x0000> ite::KVector2<double>&, Kite::KVector2<double> const&)+23>: fldl 0x8(%eax)
0x401b9c <+0x0000> ite::KVector2<double>&, Kite::KVector2<double> const&)+26>: mov 0xc(%ebp),%eax
0x401b9f <+0x0000> ite::KVector2<double>&, Kite::KVector2<double> const&)+29>: fldl 0x8(%eax)
0x401ba2 <+0x0000> ite::KVector2<double>&, Kite::KVector2<double> const&)+32>: faddp %st,%st(1)
0x401ba4 <+0x0000> ite::KVector2<double>&, Kite::KVector2<double> const&)+34>: mov 0x8(%ebp),%eax
0x401ba7 <+0x0000> ite::KVector2<double>&, Kite::KVector2<double> const&)+37>: fstpl 0x8(%eax)
0x401baa <+0x0000> ite::KVector2<double>&, Kite::KVector2<double> const&)+40>: mov 0x8(%ebp),%eax
0x401bad <+0x0000> ite::KVector2<double>&, Kite::KVector2<double> const&)+43>: pop %ebp
0x401bae <+0x0000> ite::KVector2<double>&, Kite::KVector2<double> const&)+44>: ret
تا اینجا مشخصه که از AVX استفاده نشده.
در رابطه با کامپایلر هم بگم که من قصد استفاده از VC رو ندارم(حداقل فعلا ندارم).
با gcc 4.8 هنوز امتحان نکردم ولی بزودی امتحان میکنم.
FastCode
یک شنبه 22 اردیبهشت 1392, 12:36 عصر
الان که داشتم دنبال علت میگشتم این رو پیدا کردم:
https://github.com/p12tic/libsimdpp
همین function ها توش پیاده سازی شده.البته به شکل دیگه.بد نیست بهش یه نگاه بندازید.
License Boost(تقریبا همه کاری آزاده)
UfnCod3r
یک شنبه 22 اردیبهشت 1392, 12:55 عصر
کلا دو تا عمل جمع بیشتر نیست با حالت عادی و SSE فرق چندانی نداره .
ولی من هنوزم نفهمیدم شما بردار دو بعدی double می خوای چی کار .:متفکر:
God of War 2
یک شنبه 22 اردیبهشت 1392, 13:06 عصر
FastCode یه نگاه کلی به سورس ها انداختم چیزی که تا الان متوجه شدم اینه که فقط از SSE 3 برای عملیات جمع و ... استفاده کرده.
inline float64x2 hadd2(float64x2 a, float64x2 b)
{
#if SIMDPP_USE_SSE3
return _mm_hadd_pd(a, b);
#else
return SIMDPP_NOT_IMPLEMENTED2(a, b);
#endif
}
شاید دلیلی داره که از SSE 2 استفاده نکرده!
---------
__UFNHGGI_H__ من نیازی به بردار 2 بعدی Double ندارم این کلاس بخشی از یک فریم ورک 2 بعدی هست که باید پیاده سازی بشه البته نمونه صحیح (int) و بدون نقطه اعشار هم در کنارش هست.
در رابطه با سرعت هم حتی اگر فرقی نباشه باز هم سرعت ها باید یکسان باشند نه کمتر (3 برابر کمتر)
FastCode
یک شنبه 22 اردیبهشت 1392, 13:17 عصر
FastCode یه نگاه کلی به سورس ها انداختم چیزی که تا الان متوجه شدم اینه که فقط از SSE 3 برای عملیات جمع و ... استفاده کرده.
inline float64x2 hadd2(float64x2 a, float64x2 b)
{
#if SIMDPP_USE_SSE3
return _mm_hadd_pd(a, b);
#else
return SIMDPP_NOT_IMPLEMENTED2(a, b);
#endif
}
شاید دلیلی داره که از SSE 2 استفاده نکرده!
---------
__UFNHGGI_H__ من نیازی به بردار 2 بعدی Double ندارم این کلاس بخشی از یک فریم ورک 2 بعدی هست که باید پیاده سازی بشه البته نمونه صحیح (int) و بدون نقطه اعشار هم در کنارش هست.
در رابطه با سرعت هم حتی اگر فرقی نباشه باز هم سرعت ها باید یکسان باشند نه کمتر (3 برابر کمتر)
نمیتونم دلیلی بیارم.من زیاد SSE کار نکردم.
ولی در مورد پیاده سازی کردن توابعی که نیاز ندارید:
http://en.wikipedia.org/wiki/You_aren%27t_gonna_need_it
UfnCod3r
یک شنبه 22 اردیبهشت 1392, 13:23 عصر
اون hadd فرق داره ها کارش ی چیز دیگست
http://msdn.microsoft.com/en-us/library/fyazcbxy%28v=vs.80%29.aspx
من امتحان کردم میشه گفت 1.1 برابر سریع تر بود چطور برا شما کند تره :متعجب::متعجب::متعجب:
God of War 2
یک شنبه 22 اردیبهشت 1392, 13:32 عصر
FastCode
جالب بود اما اینجا صدق نمیکنه چون این کلاس کاملا مفیده و نیاز هست. کلاسهای دیگری در فریم ورک وجود داره که پتانسیل استفاده از SSE رو دارند و با نوع double هم کار میکنند.
__UFNHGGI_H__
درسته. من داکمنتشو ندیده بودم.
(B1 + B0, A1 + A0)
با gcc امتحان کردید و دقیقا همون توابعی که من گذاشته بودم؟
UfnCod3r
یک شنبه 22 اردیبهشت 1392, 13:41 عصر
من GCC ندارم با VC امتحان کردم
__declspec(align(16)) struct Vec2
{
double xy[2];
Vec2& operator += (const Vec2& v);
Vec2& operator -= (const Vec2& v);
};
#if 1
Vec2& Vec2::operator+=( const Vec2& v )
{
_mm_store_pd(this->xy, _mm_add_pd(_mm_load_pd(this->xy), _mm_load_pd(v.xy)));
return *this;
}
Vec2& Vec2::operator-=( const Vec2& v )
{
_mm_store_pd(this->xy, _mm_sub_pd(_mm_load_pd(this->xy), _mm_load_pd(v.xy)));
return *this;
}
#else
Vec2& Vec2::operator+=( const Vec2& v )
{
xy[0] += v.xy[0];
xy[1] += v.xy[1];
return *this;
}
Vec2& Vec2::operator-=( const Vec2& v )
{
xy[0] -= v.xy[0];
xy[1] -= v.xy[1];
return *this;
}
#endif
اسمبلشون هم این شد
movapd xmm0, XMMWORD PTR [ecx]
addpd xmm0, XMMWORD PTR [eax]
movapd XMMWORD PTR [eax], xmm0
fld QWORD PTR [eax]
fsub QWORD PTR [ecx]
fstp QWORD PTR [eax]
fld QWORD PTR [eax+8]
fsub QWORD PTR [ecx+8]
fstp QWORD PTR [eax+8]
God of War 2
یک شنبه 22 اردیبهشت 1392, 14:00 عصر
movapd xmm0, XMMWORD PTR [ecx]
addpd xmm0, XMMWORD PTR [eax]
movapd XMMWORD PTR [eax], xmm0
من متوجه نمیشم چطور قبل از اینکه رجیستر xmm1 رو تخصیص بده دستور addpd رو صدا زده! در حقیقت اینجا از 1 رجیستر xmm استفاده شده و مقدار دوم مستقیما از رجیستر eax خونده شده. نمیدونم چنین چیزی در gcc قابل پیاده سازی باشه یا نه و اینکه اصلا استاندارد باشه (معمولا micro$oft انحصار طلبه و حتی اینجا هم میشه نمونش رو دید)
UfnCod3r
یک شنبه 22 اردیبهشت 1392, 14:11 عصر
برای جمع و ضرب و امثالش با باید دو یا طرف ثبات باشن یا یکی ثبات و یکی ادرس حافظه
الان ک اون کد gcc رو نگاه کردم دیدم اره مثل این که اون ازین کارا بلد نیست :متفکر:
:قلب::قلب:microsoft:قلب::قلب:
God of War 2
یک شنبه 22 اردیبهشت 1392, 14:25 عصر
برای جمع و ضرب و امثالش با باید دو یا طرف ثبات باشن یا یکی ثبات و یکی ادرس حافظه
در رابطه با SSE چنین چیزی درست نیست. یعنی هر دو طرف باید رجیستر های 128 بیتی xmm باشند.
http://en.wikipedia.org/wiki/Streaming_SIMD_Extensions
UfnCod3r
یک شنبه 22 اردیبهشت 1392, 14:41 عصر
همین مثال تو ویکیپدیا هم همین طوره :متفکر:
movaps xmm0, [v1] ;xmm0 = v1.w | v1.z | v1.y | v1.x
addps xmm0, [v2] ;xmm0 = v1.w+v2.w | v1.z+v2.z | v1.y+v2.y | v1.x+v2.x movaps [vec_res], xmm0
فکر کنم خود VC ی کارایی می کنه :متفکر:
FastCode
یک شنبه 22 اردیبهشت 1392, 15:25 عصر
من قبلا نمونه کد SSE مثل این دیدم.در AVX که خیلی جالبتر شده و میتونید یک Vector به instruction بدید که آدرسها رو از توش بخونه
سپول
یک شنبه 22 اردیبهشت 1392, 18:24 عصر
این کد واسه این نسخه SSE ایش کندتر می شه که شما بیشتر دارید درون رجیستر ها متغیرهای floating-point رو لود می کنه تا محاسبه .
یک مورد که خیلی ها اشتباه برداشت می کنند اینه که فکر می کنند هر چیزی رو SSe کنند سرعتش بیشتر می شه ولی اینجوری نیست. در محاسبات SSE تا اونجا که می شه دیتا رو باید آماده این کار کرد از قبل، و به صورت دسته ای یک جا محاسباتی رو انجام داد.
_mm_store_pd(&left.component[KVC_X],
_mm_add_pd(_mm_load_pd(left.component), _mm_load_pd(right.component)));
الان شما در این کد برای یک محاسبه ساده (add) دوبار دیتا رو از توی حافظه به رجیسترهای XMM انتقال دادید که این سرعتش از لود کردن در رجیسترهای FPU و محاسبه معمولی a+b کمتر هست.
در مورد اسمبلی هم کاربر UFNG... درست می گه، از همون توابع instrinsic های کامپایلر استفاده کنید، هم همه جا کامپایل می شه هم اینکه کامپایلر می فهمه چه خبره و بهتر می تونه بهینه کنه.
God of War 2
یک شنبه 22 اردیبهشت 1392, 22:04 عصر
این کد واسه این نسخه SSE ایش کندتر می شه که شما بیشتر دارید درون رجیستر ها متغیرهای floating-point رو لود می کنه تا محاسبه .
یک مورد که خیلی ها اشتباه برداشت می کنند اینه که فکر می کنند هر چیزی رو SSe کنند سرعتش بیشتر می شه ولی اینجوری نیست. در محاسبات SSE تا اونجا که می شه دیتا رو باید آماده این کار کرد از قبل، و به صورت دسته ای یک جا محاسباتی رو انجام داد.
من برداشتم از حرف شما اینه که باید از 8 رجیستر بصورت همزمان استفاده کرد. یعنی هر 8 تا اماده بشن بعد عملیات مختلف روشون انجام بشه اما چنین چیزی حداقل برای یک کلاس بردار 2 بعدی معمولی قابل پیاده سازی نیست!
الان شما در این کد برای یک محاسبه ساده (add) دوبار دیتا رو از توی حافظه به رجیسترهای XMM انتقال دادید که این سرعتش از لود کردن در رجیسترهای FPU و محاسبه معمولی a+b کمتر هست.
در مورد اسمبلی هم کاربر UFNG... درست می گه، از همون توابع instrinsic های کامپایلر استفاده کنید، هم همه جا کامپایل می شه هم اینکه کامپایلر می فهمه چه خبره و بهتر می تونه بهینه کنه.
قطعا توابع instrinsicبرای کامپایلر قابل هضم تر هست ولی مشکل اینجاست که این توابع (حداقل در gcc) خوب پیاده سازی نشدن. اینو میشه در کدهای اسمبلی تولید شده دید و در مقایسه با کدهای اسمبلی تولید شده توسط VC یکم دچار تردید میشید. به هر حال من امیدوارم بتونم کلاس برداری سریعتر از حالت معمول FPU پیاده کنم گرچه تا الان به نتیجه ایی نرسیدم.
در رابطه بااون کد آیا پیاده سازی یا روش بهتری وجود داره؟
UfnCod3r
یک شنبه 22 اردیبهشت 1392, 23:31 عصر
خب وقتی دو تا جمع بیشتر نیست تاثیری نداره
دو تا double ارزش لود شدن تو XMM رو ندارن مخصوصا اینکه کلا فقط ی عمل جمع روشون انجام میشه :لبخند:
اما مثلا جمع بردار 4 بعدی از نوع float با SSE خیلی خوب میشه
fld DWORD PTR [ecx]
fadd DWORD PTR [eax]
fstp DWORD PTR [eax]
; 21 : xyzw[1] += v.xyzw[1];
fld DWORD PTR [ecx+4]
fadd DWORD PTR [eax+4]
fstp DWORD PTR [eax+4]
; 22 : xyzw[2] += v.xyzw[2];
fld DWORD PTR [ecx+8]
fadd DWORD PTR [eax+8]
fstp DWORD PTR [eax+8]
; 23 : xyzw[3] += v.xyzw[3];
fld DWORD PTR [ecx+12]
fadd DWORD PTR [eax+12]
fstp DWORD PTR [eax+12]
تو حالت عادی 4 بار لود میشه تو fpu چهار بار جمع انجام میشه و 4 بارم از fpu ب رم . کلا میشه 12 دستور + اینکه چند تا جمع رو eax,acx هم میشه
خب اینو با SSE بنویسیم خیلی سریع میشه
movaps xmm0, XMMWORD PTR [ecx]
addps xmm0, XMMWORD PTR [eax]
movaps XMMWORD PTR [eax], xmm0
یا مثلا تابع Lerp که خوراکش SSE هست :لبخند:
عادیش 24 تا دستور میشه + چند تا جمع
fld DWORD PTR [edx]
fsub DWORD PTR [eax]
fld DWORD PTR _f$[ebp]
fld ST(0)
fmulp ST(2), ST(0)
fld DWORD PTR [eax]
faddp ST(2), ST(0)
fxch ST(1)
fstp DWORD PTR [ecx]
; 36 : dst.xyzw[1] = from.xyzw[1] + (to.xyzw[1] - from.xyzw[1]) * f;
fld DWORD PTR [edx+4]
fsub DWORD PTR [eax+4]
fmul ST(0), ST(1)
fadd DWORD PTR [eax+4]
fstp DWORD PTR [ecx+4]
; 37 : dst.xyzw[2] = from.xyzw[2] + (to.xyzw[2] - from.xyzw[2]) * f;
fld DWORD PTR [edx+8]
fsub DWORD PTR [eax+8]
fmul ST(0), ST(1)
fadd DWORD PTR [eax+8]
fstp DWORD PTR [ecx+8]
; 38 : dst.xyzw[3] = from.xyzw[3] + (to.xyzw[3] - from.xyzw[3]) * f;
fld DWORD PTR [edx+12]
fsub DWORD PTR [eax+12]
fmulp ST(1), ST(0)
fadd DWORD PTR [eax+12]
fstp DWORD PTR [ecx+12]
ولی SSE ـیش فقط همین 8 تا میشه
movaps xmm2, XMMWORD PTR [ecx]
subps xmm2, XMMWORD PTR [eax]
movss xmm0, DWORD PTR _f$[ebp]
mov edx, DWORD PTR _dst$[ebp]
shufps xmm0, xmm0, 0
mulps xmm2, xmm0
addps xmm2, XMMWORD PTR [eax]
movaps XMMWORD PTR [edx], xmm2
خلاصه بهتره بردار دو بعدی با SSE رو بی خیال شی .:لبخند:
سپول
دوشنبه 23 اردیبهشت 1392, 00:12 صبح
نه... کاربر __UFNHGGI_H__ هم روش درست کاربرد SSE رو متوجه نشده .
منظور من اینه که SSE برای اون کاری که می خوای طراحی نشده. برای محاسبات فله ای طراحی شده.
یعنی اگه می خوای کلاس برداری سریعتر بسازی این راهش نیست، اول یک کلاس با همون fpu معمولی درست کن که کارهات رو راه بندازه.
وقتی برنامه یا انجین رو داری می نویسی به جاهایی بر می خوری که احتیاج به محاسبات سنگینتر هست. مثلا 1000 اسپرایت رو می خوای نقل مکان بدی. اونجا هست که باید اطلاعات رو مرتب کنی به طوری که برای عملیات SSE آماده باشه. بعد تو یک حلقه با استفاده از SSE می تونی کدی بزنی که این 1000 نقل مکان رو به شکل بسیار سریعتری انجام بده.
یک مثال ساده می زنم:
به فرض جایی در کد تعداد 1000 بردار داری (x, y) که می خواهی طولشون رو به سرعت حساب کنی. فرمول طول میشه :
length(x,y) = sqrt(x*x + y*y);
حالا به جای اینکه یک کلاس بنویسی که بیاد length رو برای هر بردار حساب کنه به این شکل (می تونه درون تابع SSE باشه یا هر چیز دیگه) :
void scale_sprites(float result[1000], const vector2d* vs)
{
for (int i = 0; i < 1000; i++) {
result[i] = vs[i].Length();
}
}
اگر می خوایی واقعا با SSE کدی بنویسی که شاید تا 3 برابر سریعتر باشه، باید قبل از عملیات، اطلاعات رو جور دیگه ای مرتب کنی (که بهش می گن structure of arrays) ، یعنی x ها رو در یک آرایه بزرگ و y ها رو هم در یک آرایه بزرگ دیگه بریزی.
کد زیر رو ببین که با SSE و فقط 4 instruction، چهار بردار رو طولش رو یکجا حساب می کنه (دقت کن که 4 تا 4 تا جلو می ریم در این نسخه) :
struct sprite_data
{
float x[1000];
float y[1000];
};
void scale_sprites(float result[1000], const struct sprite_data* input)
{
for (int i = 0; i < 1000; i+=4) {
__m128 mx = _mm_load_ps(&input->x[i]);
__m128 my = _mm_load_ps(&input->y[i]);
/* r = sqrt((x*x) + (y*y)) = length of vector */
__m128 mrsqr = _mm_add_ps(_mm_mul_ps(mx, mx), _mm_mul_ps(my, my));
_mm_store_ps(&result[i], _mm_sqrt_ps(mrsqr));
}
}
راه درست استفاده از SSE و کلا SIMD اینجوری هست، و باید بدونی کجا بهش احتیاج داری و دقیقا چی می خوای که بتونی data رو بر همون اساس طراحی و مرتب کنی.
خلاصه اینکه اول برنامه رو بنویس بعد ببین کجاها محاسبات سنگین داری. سپس برای اونها الگوریتم SSE همراه با دیتا درست پیاده کن.
ولی مشکل اینجاست که این توابع (حداقل در gcc) خوب پیاده سازی نشدن.
خیلی هم خوب پیاده شدن. در واقع کامپایلر مایکروسافت هست که بدترین کد رو می سازه، نتیجه تحقیق زیر رو ببین:
http://www.liranuna.com/sse-intrinsics-optimizations-in-popular-compilers/
نکته دیگه اینکه چرا از double استفاده می کنی ؟! float برای بازی زیاد هم هست، double فقط سرعت رو میاره پایین و حافظه بیشتری اشغال می کنه. تازه در یک رجیستر SSE 4 تا float می تونی جای بدی و روش عملیات انجام بدی که فرقش خیلی زیاده.
معمولا فقط در شبیه سازی های علمی و اینجوری چیزها از double استفاده می کنند نه بازی.
UfnCod3r
دوشنبه 23 اردیبهشت 1392, 08:59 صبح
اره راست میگی منم جدیدا قاطی کردم همه چی رو می خوام SSE کنم .
فقط در مورد اون لینک ک دادی فکر کنم طرف با:قلب: microsoft:قلب: بد جوری درگیره
نمونش همین کد
__m128 m = _mm_set_ps(4, 3, 2, 1);
نوشته MSVC اینو تولید می کنه :متعجب: (اخه مگه دیوانست )
movss xmm2, DWORD PTR __real@40400000 ; 3.0f
movss xmm3, DWORD PTR __real@40800000 ; 4.0f
movss xmm0, DWORD PTR __real@3f800000 ; 1.0f
movss xmm1, DWORD PTR __real@40000000 ; 2.0f
unpcklps xmm0, xmm2
unpcklps xmm1, xmm3
unpcklps xmm0, xmm1
درصورتی ک اگه خودتون امتحان کنید می بینید اصلا چنین چیزی نیست اونم همه رو با movaps ی جا می فرسته
تو بقیه موارد هم درست عمل می کنه عمرا اگه VC از GCC عقب تر باشه .
اون طرف امده با GCC4 امتحان کرده و VC2008 (لابد رو دیباگم بوده :قهقهه:) نامردیه دیگه با 2010 امتحان کنید . هرچند ک بعید می دونم 2008 هم چنین کد هایی تحویل بده .
این جناب بیل هم یکم خصیص هست وگرنه من جاش بودم کل GCC و برنامه نویس هایش را یکجا می خریدم .:شیطان::شیطان::شیطان: :بامزه:
سپول
سه شنبه 24 اردیبهشت 1392, 18:05 عصر
اون تست مال سال 2009 بوده. و با ویژوال استودیو 2008 کامپایل کرده. الان حتما نه فقط کامپایلر مایکروسافت بلکه بقیه کامپایلر ها هم بهینه تر شدن.
ولی اصل قضیه اینه که gcc اصلا عقب نیست مخصوصا در زمینه SSE از vs2008 بهتره. در ویژوال استودیو 2010 این قضیه خیلی بهتر شده و مخصوصا سرعت کد خروجی SSE خیلی بهینه تر شده.
مشکل اصلیه کامپایلر مایکروسافت برای من اینه کدهای C99 رو نمی تونه کامپایل کنه که با توجه به اینکه C99 یک استانداردیه که بخشی از C++ رو در برداره. ساپورت نکردن مایکروسافت کاملا دلیل استراتژیک و سیساستی داشته (مثل بقیه کارهاش) و برای من قابل قبول نیست.
vBulletin® v4.2.5, Copyright ©2000-1404, Jelsoft Enterprises Ltd.