یک مفهوم پیشین مقادیر ممیز شناور بستهبندیشده را معرفی کرده بود: چندین عدد که در یک ثبات xmm نگه داشته میشوند و بهصورت موازی و لِینبهلِین روی آنها عمل میشود.
همین ثباتهای xmm میتوانند اعداد صحیح بستهبندیشده را هم در خود نگه دارند.
بیشتر مدل ممیز شناور SIMD بدون تغییر به اعداد صحیح بستهبندیشده منتقل میشود:
با این حال، نحوهی نگارش کمی متفاوت است:
p که نشان میدهد دستور روی دادههای بستهبندیشده عمل میکند.add، mul و غیره).اعداد صحیح بستهبندیشده چهار اندازهی لِین دارند و هر کدام پسوند خودش را دارد:
| عرض لِین | بایت | لِینها در ۱۲۸ بیت | پسوند |
|---|---|---|---|
| بایت | ۱ | ۱۶ | b |
| کلمه | ۲ | ۸ | w |
| کلمهی دوگانه | ۴ | ۴ | d |
| کلمهی چهارگانه | ۸ | ۲ | q |
برای نمونه:
| دستور | معنا |
|---|---|
paddb |
add بستهبندیشده، لِینهای ۸ بیتی (۱۶ لِین) |
paddw |
add بستهبندیشده، لِینهای ۱۶ بیتی (۸ لِین) |
paddd |
add بستهبندیشده، لِینهای ۳۲ بیتی (۴ لِین) |
paddq |
add بستهبندیشده، لِینهای ۶۴ بیتی (۲ لِین) |
برخی دستورها لِینهایی با یک اندازه را بهعنوان ورودی میگیرند، اما لِینهایی با اندازهی دیگر را بهعنوان خروجی تولید میکنند. آنها از همان قرارداد کلی پیروی میکنند، اما با دو پسوند اندازه. اولی اندازهی لِین ورودی را نشان میدهد و دومی اندازهی لِین خروجی را:
| دستور | معنا |
|---|---|
pmovsxwd |
movsx بستهبندیشده، از لِینهای ۱۶ بیتی به لِینهای ۳۲ بیتی |
pmuldq |
mul بستهبندیشده، از لِینهای ۳۲ بیتی به لِینهای ۶۴ بیتی |
دو دستور با اسم صحیح، ۱۲۸ بیت را بین یک ثبات xmm و حافظه کپی میکنند:
| دستور | توضیح |
|---|---|
movdqa |
کپی اعداد صحیح بستهبندیشده به یک مکان همتراز یا از آن |
movdqu |
کپی اعداد صحیح بستهبندیشده به یک مکان ناهمتراز یا از آن |
رفتار آنها مانند movaps و movups است: movdqa روی یک نشانی ناهمتراز دچار خطا میشود، در حالی که movdqu هر نشانی را میپذیرد.
هر چهار دستور ۱۲۸ بیت را بدون تفسیر کردن آنها کپی میکنند.
استفاده از جفت با اسم صحیح با دادههای صحیح یک قرارداد است، نه یک الزام.
dq در اینجا مخفف double-qword است، یعنی ۱۲۸ بیت (۱۶ بایت).
جمع و تفریق از قاعدهی نامگذاری پیروی میکنند:
paddb xmm0, xmm1 ; 16 lanes: each 8-bit, xmm0 += xmm1
paddw xmm2, xmm3 ; 8 lanes: each 16-bit, xmm2 += xmm3
psubd xmm4, xmm5 ; 4 lanes: each 32-bit, xmm4 -= xmm5
psubq xmm6, xmm7 ; 2 lanes: each 64-bit, xmm6 -= xmm7
شکل جداگانهای برای علامتدار و بدون علامت وجود ندارد.
در مکمل دو، جمع و تفریق صرفنظر از اینکه لِینها علامتدار خوانده شوند یا بدون علامت، همان بیتها را تولید میکنند، بنابراین یک دستور برای هر دو کافی است.
تفسیر آن به عهدهی خودتان است، دقیقاً مانند add و sub اسکالر.
این دستورها هنگام سرریز، مانند همتاهای اسکالرشان، دور میزنند.
یک لِین ۸ بیتی مقادیر را بهپیمانهی ۲۵۶ نگه میدارد، بنابراین paddb روی 200 + 100 نتیجهی 300 - 256 = 44 را تولید میکند و بیتهایی را که جا نمیشوند دور میریزد.
توجه کنید که آن بیتهای اضافی به لِین بعدی منتقل نمیشوند. هر لِین جدا از بقیه پردازش میشود، هرچند همه در یک ثبات مشترک قرار دارند.
SIMD اعداد صحیح عملیاتی را اضافه میکند که SIMD ممیز شناور ندارد: جمع و تفریق اشباعی، که بهجای دور زدن، مقدار را محدود میکند. نتیجهای بالاتر از بازهی لِین، به بزرگترین مقدار قابل نگهداری در آن لِین تبدیل میشود و نتیجهای پایینتر از بازه، به کوچکترین مقدار.
شکلهای اشباعی s (علامتدار) یا us (بدون علامت) را پیش از پسوند اندازه قرار میدهند:
| دستور | معنا |
|---|---|
paddsb |
add اشباعی، لِینهای ۸ بیتی علامتدار |
paddusb |
add اشباعی، لِینهای ۸ بیتی بدون علامت |
psubsw |
sub اشباعی، لِینهای ۱۶ بیتی علامتدار |
psubusw |
sub اشباعی، لِینهای ۱۶ بیتی بدون علامت |
بازهی محدودسازی، کل بازهی قابل نمایش برای عدد صحیحی با آن اندازه و علامت است. برای یک بایت:
[0, 255] محدود میشوند: paddusb روی 200 + 100 مقدار 255 را میدهد و psubusb روی 5 - 10 مقدار 0 را.[-128, 127] محدود میشوند: paddsb روی 100 + 50 مقدار 127 را میدهد.اشباع زمانی اهمیت دارد که یک لِین مقداری کرانمند، مانند کانال یک پیکسل یا یک نمونهی صوتی، را نگه دارد. دور زدن یک پیکسل بیشازحد روشن را تیره میکند، در حالی که محدودسازی آن را در بیشترین روشنایی نگه میدارد، که همان نتیجهی مطلوب شماست.
جمع و تفریق اشباعی فقط برای لِینهای بایت و کلمه وجود دارند، نه برای کلمهی دوگانه یا کلمهی چهارگانه.
ضرب دو مقدار N بیتی میتواند حاصلضربی 2N بیتی تولید کند، اما لِین مقصد فقط N بیت عرض دارد. عملیات SIMD این مسئله را با مشخص کردن اینکه کدام نیمهی حاصلضرب نگه داشته شود حل میکنند: یا N بیت پایین یا N بیت بالا.
برای لِینهای ۱۶ بیتی، سه دستور به کار میرود:
| دستور | معنا |
|---|---|
pmullw |
mul، لِینهای ۱۶ بیتی، نگه داشتن ۱۶ بیت پایین هر حاصلضرب |
pmulhw |
mul، لِینهای ۱۶ بیتی، نگه داشتن ۱۶ بیت بالا، عملوندهای علامتدار |
pmulhuw |
mul، لِینهای ۱۶ بیتی، نگه داشتن ۱۶ بیت بالا، عملوندهای بدون علامت |
۱۶ بیت پایین یک حاصلضرب، صرفنظر از اینکه عملوندها علامتدار خوانده شوند یا بدون علامت، یکسان است، بنابراین فقط یک pmullw وجود دارد.
اما ۱۶ بیت بالا بسته به علامتدار بودن نتیجه متفاوت است. به همین دلیل ضرب نیمهی بالا شکلهای جداگانهای برای ضرب علامتدار و بدون علامت دارد:
pmulhw، برای ضرب علامتدار.pmulhuw، با یک u اضافه، برای ضرب بدون علامت.به نحوهی نگارش توجه کنید:
p، برای عدد صحیح بستهبندیشده.mul.h، که نشان میدهد بیتهای بالا («high») نتیجه انتخاب میشوند.u اختیاری، اگر نتیجه باید بدون علامت تفسیر شود (یعنی علامت آن گسترش نیافته است).w، که نشان میدهد عملیاتی روی کلمه (۱۶ بیتی) است.ضرب بستهبندیشده برای کلمهها دقیقاً از قاعدهی بالا پیروی میکند.
ضرب کلمهی دوگانه (۳۲ بیتی) هم از این قاعده پیروی میکند، اما تنها گونهای که نیمهی پایین را انتخاب میکند وجود دارد: pmulld.
هیچ گونهای از ضرب کلمهی دوگانه وجود ندارد که بیتهای بالا را انتخاب کند. با این حال، گونههایی وجود دارند که ضرب را گسترش میدهند و حاصلضرب کامل ۶۴ بیتی لِینهای با اندیس زوج (یعنی لِینهای در موقعیت ۰ و ۲) را ذخیره میکنند:
| دستور | معنا |
|---|---|
pmuludq |
ضرب عنصرهای ۳۲ بیتی با اندیس زوج، بدون علامت، به ۲ حاصلضرب کامل ۶۴ بیتی |
pmuldq |
ضرب عنصرهای ۳۲ بیتی با اندیس زوج، علامتدار، به ۲ حاصلضرب کامل ۶۴ بیتی |
توجه کنید که در نحوهی نگارش از dq استفاده میشود، که ممکن است برای ضرب بدون علامت یک u پیش از آن بیاید.
علتش این است که این دستورها لِینهای dword را میگیرند و لِینهای qword را تولید میکنند.
تقسیم بستهبندیشده برای اعداد صحیح وجود ندارد. کدی که به آن نیاز دارد، مقادیر را به ممیز شناور تبدیل میکند، تقسیم میکند و دوباره برمیگرداند.
معادلهای بستهبندیشدهای برای movsx و movzx وجود دارد.
آنها از همان نحوهی نگارشی پیروی میکنند که برای دستورهایی گفتیم که اندازهی لِین ورودی و خروجیشان متفاوت است:
pmovsxwd xmm0, xmm1 ; 4 words -> 4 dwords, sign-extended
pmovzxbw xmm0, xmm1 ; 8 bytes -> 8 words, zero-extended
توجه کنید که تعداد لِینها با عرض بزرگتر (خروجی) تعیین میشود.
دستور همان تعداد لِین را از بخش پایین مبدأ میخواند.
این همان معناشناسیای است که پیشتر برای دستورهای cvt بستهبندیشده میان ممیز شناور تکدقت و دودقت دیدهایم.
دستورهایی برای تبدیل میان اعداد صحیح علامتدار ۳۲ بیتی و لِینهای ممیز شناور وجود دارد.
آنها از نحوهی نگارش معمول برای دستورهای cvt و cvtt پیروی میکنند، اما بهجای si، عدد صحیح بستهبندیشده با dq نمایش داده میشود:
cvtdq2ps xmm0, xmm1 ; convert 32-bit signed integers in xmm1 to 32-bit floats in xmm0
cvtdq2pd xmm2, xmm3 ; convert 32-bit signed integers in xmm3 to 64-bit floats in xmm2
cvtps2dq xmm4, xmm5 ; convert 32-bit floats in xmm5 to 32-bit signed integers in xmm4
دستورهایی که برای نمایش اعداد صحیح بستهبندیشده از pi استفاده میکنند، در ثباتهای قدیمی mmx مینویسند و در x86-64 عملاً منسوخ شدهاند.
شکلهای dq را ترجیح دهید، که از ثباتهای xmm استفاده میکنند.
همان نکاتی که برای تبدیل اسکالر میان اعداد ممیز شناور و صحیح گفته شد، اینجا هم معتبر است.
اعداد ممیز شناور بر اساس یک ثبات ویژه به اسم MXCSR گرد میشوند، که نمیتوانید حالت آن را در ورود به تابع فرض کنید.
یک گونهی cvtt (با یک t اضافه) وجود دارد که همیشه نتیجه را قطع میکند.
همچنین میتوانید از round استفاده کنید تا مقادیر ممیز شناور بستهبندیشده پیش از تبدیل در وضعیت معلومی باشند.
مقدار کنترل گرد کردن همانند round اسکالر است و نحوهی نگارش برای مقادیر ممیز شناور بستهبندیشده هم یکسان است:
roundps xmm0, xmm1, 1 ; xmm0 = floor(xmm1), packed 32-bit floats
roundpd xmm2, xmm3, 2 ; xmm2 = ceil(xmm3), packed 64-bit floats
شما هستهی دیجیتال یک کنسول میکس کوچک در یک استودیوی ضبط را مینویسید.
صدا به شکل جریانی از نمونهها میرسد. هر نمونه عددی است که موقعیت مخروط بلندگو را در یک لحظه توصیف میکند. یک کنسول یک بلوک کامل از نمونهها را یکباره پردازش میکند و عملیات یکسانی را روی هر نمونه اعمال میکند.
پنج کار در پیش دارید که هر کدام روی بلوکی از نمونههای نگهداریشده در حافظه کار میکند. عملوندها را از طریق آدرسهای حافظه دریافت میکنید و پاسخ خود را از طریق آدرس نتیجه مینویسید.
محاسبات این تمرین باید با دستورهای SIMD انجام شود، نه با عملیات اسکالر.
استودیو گاهی سازها را روی ترکهای مختلف ضبط میکند. برای پخش آنها از همان بلندگوها، دو ترک را در یک ترک جمع میکنید.
تابع mix_tracks را پیادهسازی کنید که دو ترک را نمونه به نمونه با هم جمع میکند.
هر ترک بلوکی از ۸ نمونهی ۱۶ بیتی علامتدار است.
مجموعی که از بازهی ۱۶ بیتی علامتدار فراتر میرود باید اشباع شود، نه اینکه دور بزند.
این تابع این آرگومانها را به این ترتیب میگیرد:
result: آدرس حافظهی همتراز با ۱۶ بایت برای بافری که ۸ نمونهی میکسشده در آن نوشته میشود.track_a: آدرس حافظهی همتراز با ۱۶ بایت ترک اول، با ۸ نمونهی ۱۶ بیتی علامتدار.track_b: آدرس حافظهی همتراز با ۱۶ بایت ترک دوم، با ۸ نمونهی ۱۶ بیتی علامتدار.track_a = {10000, 30000, 20000, -30000, -20000, -100, 100, 0}
track_b = { 5000, 5000, 20000, -5000, -20000, 100, -100, 0}
result = {15000, 32767, 32767, -32768, -32768, 0, 0, 0}
این تابع مقدار بازگشتی ندارد.
وقتی دو میکروفون همزمان ضبط میکنند، هر کدام نسخهی محوی از منبع دیگری را هم برمیدارد. به این نسخهی ناخواسته «نشتی» میگویند. وقتی این نشتی اندازهگیری شد، باید دوباره از ترک کم شود تا ترک پاکسازی شود.
تابع remove_bleed را پیادهسازی کنید که بلوک نشتی را نمونه به نمونه از بلوک ترک کم میکند.
هر بلوک ۸ نمونهی ۱۶ بیتی علامتدار است و تفریق باید اشباع شود.
این تابع این آرگومانها را به این ترتیب میگیرد:
result: آدرس حافظهای که ۸ نمونهی پاکشده در آن نوشته میشود (همترازی تضمین نشده است).track: آدرس حافظهی همتراز با ۱۶ بایت ترک ضبطشده، ۸ نمونهی ۱۶ بیتی علامتدار.bleed: آدرس حافظهی نشتیای که باید حذف شود، ۸ نمونهی ۱۶ بیتی علامتدار (همترازی تضمین نشده است).track = {10000, -30000, 5000, 32767, 0, 200, -200, 15000}
bleed = { 2000, 5000, 5000, -5, 0, 300, 300, 5000}
result = { 8000, -32768, 0, 32767, 0, -100, -500, 10000}
این تابع مقدار بازگشتی ندارد.
بالای هر کانال یک «نشانگر سطح» قرار دارد، نواری کوچک که روشناییاش نشان میدهد آن کانال چقدر بلند است. پنل جلویی یک نشانگر اصلی دارد که باید به اندازهی مجموع دو کانال بدرخشد. برای راهاندازی آن، دو بلوک نشانگر را با هم جمع میکنید.
توجه کنید که نشانگر روشن به جای دور زدن و بازگشت به تاریکی، در روشنایی کامل میماند؛ بنابراین جمع اشباع میشود.
تابع combine_meters را پیادهسازی کنید که دو بلوک نشانگر را نمونه به نمونه جمع میکند.
این تابع این آرگومانها را به این ترتیب میگیرد:
result: آدرس حافظهی همتراز با ۱۶ بایت که ۱۶ مقدار ترکیبشده در آن نوشته میشود.meter_a: آدرس حافظهی همتراز با ۱۶ بایت بلوک نشانگر کانال اول، با ۱۶ مقدار ۸ بیتی بدون علامتِ بستهبندیشده.meter_b: آدرس حافظهی همتراز با ۱۶ بایت بلوک نشانگر کانال دوم، با ۱۶ مقدار ۸ بیتی بدون علامتِ بستهبندیشده.هر بلوک ۱۶ مقدار ۸ بیتی بدون علامت است. این مقادیر باید با هم جمع شوند و در بازهی بدون علامت اشباع شوند.
meter_a = {200, 100, 50, 255, 0, 128, 10, 240, 1, 2, 3, 4, 5, 6, 7, 8}
meter_b = {100, 100, 200, 1, 0, 128, 10, 20, 1, 2, 3, 4, 5, 6, 7, 8}
result = {255, 200, 250, 255, 0, 255, 20, 255, 2, 4, 6, 8, 10, 12, 14, 16}
این تابع مقدار بازگشتی ندارد.
آهنگ نباید بهطور ناگهانی قطع شود، بلکه باید در انتها بهتدریج محو شود. بلوکی از «ضرایب بهره» در اختیار دارید، یکی به ازای هر نمونه، که پشت سر هم کمتر و کمتر میشوند.
هر ضریب کسری است که بر پایهی 65536 بیان میشود.
برای مثال، اگر نمونهای مقدار 16384 داشته باشد، در واقع 16384 / 65536 را بیان میکند که برابر 1 / 4 است.
بزرگترین مقدار قابل استفاده، 32767، کمی کمتر از یکدوم است.
از آنجا که 65536 برابر 2^16 است، ضرب یک نمونه در ضریبش و نگهداشتن «نیمهی بالا»ی حاصلضرب، sample * gain / 65536 را به دست میدهد.
تابع apply_fade را پیادهسازی کنید که هر نمونه را در ضریبش مقیاس میکند.
این تابع این آرگومانها را به این ترتیب میگیرد:
result: آدرس حافظهی همتراز با ۱۶ بایت که همهی نمونههای مقیاسشده در آن نوشته میشوند.track: آدرس حافظهی همتراز با ۱۶ بایت ترک، با n نمونهی ۱۶ بیتی علامتدار.gains: آدرس حافظهی همتراز با ۱۶ بایت ضرایب بهره، با n مقدار ۱۶ بیتی علامتدار که هر کدام در بازهی 0 تا 32767 قرار دارند.n: تعداد نمونهها، که همیشه ناصفر و مضربی از 8 است، بهصورت یک عدد صحیح ۶۴ بیتی بدون علامت.توجه کنید که ترک آرایهای است که n نمونهی ۱۶ بیتی علامتدار را نگه میدارد؛ n هرگز صفر نیست و همیشه مضربی از 8 است.
برای هر نمونه، نتیجهی مقیاسشده ۱۶ بیت «بالا»ی حاصلضرب علامتدار است: sample * gain.
track = {20000, 20000, 20000, 20000, -20000, -20000, -20000, -20000}
gains = {32767, 24576, 16384, 8192, 32767, 24576, 16384, 8192}
result = { 9999, 7500, 5000, 2500, -10000, -7500, -5000, -2500}
نگهداشتن نیمهی بالا به سمت منفی بینهایت گرد میکند.
به همین دلیل 20000 * 32767 به 9999 مقیاس میشود، در حالی که -20000 * 32767 به -10000 مقیاس میشود و نه -9999.
این تابع مقدار بازگشتی ندارد.
صدای نوازندهی درام بیش از حد بلند ضبط شده بود. برای کم کردن صدای کل ترک، هر نمونه را بر یک ثابت تقسیم میکنید.
تابع attenuate_track را پیادهسازی کنید.
این تابع این آرگومانها را به این ترتیب میگیرد:
result: آدرس حافظهی همتراز با ۱۶ بایت که نتایج بهصورت اعداد صحیح ۳۲ بیتی علامتدار در آن نوشته میشوند.samples: آدرس حافظهی همتراز با ۱۶ بایت نمونهها، n مقدار ۱۶ بیتی علامتدار.divisor: آدرس حافظهی همتراز با ۱۶ بایت چهار نسخهی یکسان از مقسومعلیه، بهصورت اعداد صحیح ۳۲ بیتی.n تعداد نمونهها است که همیشه ناصفر و مضربی از 4 است، بهصورت یک عدد صحیح ۶۴ بیتی بدون علامت.توجه کنید که samples آرایهای است که n نمونهی ۱۶ بیتی علامتدار را نگه میدارد؛ n هرگز صفر نیست و همیشه مضربی از 4 است.
مقسومعلیه بهصورت 4 نسخه از همان عدد صحیح ۳۲ بیتی ارائه میشود، که از پیش تکرار شده است تا بتواند هر چهار مسیر را یکجا پر کند.
SIMD صحیح تقسیم بستهبندیشده ندارد، بنابراین کار در قالب ممیز شناورِ بستهبندیشده انجام میشود و سپس به اعداد صحیح ۳۲ بیتی بستهبندیشده تبدیل میشود تا در result ذخیره شود.
تبدیل بازگشتی با بریدن به سمت صفر انجام میشود، بنابراین خارجقسمت 2.3 برابر 2 و -2.3 برابر -2 میشود.
samples = {300, 150, 90, -120, 100, 50, -50, 7}
divisor = {3, 3, 3, 3}
result = {100, 50, 30, -40, 33, 16, -16, 2}
این تابع مقدار بازگشتی ندارد.
در Exercism ثبتنام کنید تا x86-64 Assembly را همراه با 22 مفهوم130 تمرین و مربیگری انسانی واقعی یاد بگیرید و در آن استاد شوید، همهی اینها رایگان.