قدّم مفهوم سابق قيم الفاصلة العائمة المعبّأة: أعداد متعددة محفوظة في سجل xmm واحد، وتُعالَج على مستوى المسار وعلى التوازي.
ويمكن للسجلات xmm نفسها أن تحمل أيضًا أعدادًا صحيحة معبّأة.
ينتقل معظم نموذج SIMD الخاص بالفاصلة العائمة دون تغيير إلى الأعداد الصحيحة المعبّأة:
لكن الصياغة تختلف قليلًا:
p للدلالة على أن التعليمة تعمل على بيانات معبّأة.add وmul وغيرها.للأعداد الصحيحة المعبّأة أربعة أحجام للمسارات، ولكل منها لاحقتها الخاصة:
| عرض المسار | البايتات | المسارات في 128 بت | اللاحقة |
|---|---|---|---|
| بايت | 1 | 16 | b |
| كلمة | 2 | 8 | w |
| dword | 4 | 4 | d |
| qword | 8 | 2 | q |
على سبيل المثال:
| التعليمة | المعنى |
|---|---|
paddb |
جمع معبّأ، مسارات 8 بت (16 مسارًا) |
paddw |
جمع معبّأ، مسارات 16 بت (8 مسارات) |
paddd |
جمع معبّأ، مسارات 32 بت (4 مسارات) |
paddq |
جمع معبّأ، مسارات 64 بت (مساران) |
تأخذ بعض التعليمات مسارات بحجم معيّن مدخلًا، لكنها تُخرج بحجم آخر. وهي تتبع العُرف العام نفسه، لكن بلاحقتَي حجم. الأولى تدل على حجم مسار الإدخال، والثانية تدل على حجم مسار الإخراج:
| التعليمة | المعنى |
|---|---|
pmovsxwd |
movsx معبّأ، من مسارات 16 بت إلى مسارات 32 بت |
pmuldq |
mul معبّأ، من مسارات 32 بت إلى مسارات 64 بت |
تنسخ تعليمتان تحملان اسمًا مشتقًّا من الأعداد الصحيحة 128 بت بين سجل xmm والذاكرة:
| التعليمة | الوصف |
|---|---|
movdqa |
نسخ أعداد صحيحة معبّأة من وإلى موضع محاذى |
movdqu |
نسخ أعداد صحيحة معبّأة من وإلى موضع غير محاذى |
وهي تتصرّف مثل movaps وmovups: فتُسبّب movdqa خطأً عند عنوان غير محاذى، بينما تقبل movdqu أي عنوان.
والأربع كلها تنسخ 128 بت دون أن تفسّرها.
ويُستخدم الزوج المسمّى بأسماء الأعداد الصحيحة مع بيانات الأعداد الصحيحة بحكم العرف، لا بحكم الإلزام.
dq هنا اختصار لـ double-qword، أي 128 بت (16 بايت).
يتبع الجمع والطرح قاعدة التسمية:
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 القياسيتين.
تلتفّ هذه التعليمات عند التجاوز، مثل نظيراتها القياسية.
يحمل مسار الـ8 بت قيمًا وفق باقي القسمة على 256، لذا ينتج paddb للقيمة 200 + 100 الناتج 300 - 256 = 44، مع طرح البِتات التي لا تتّسع لها.
لاحظ أن هذه البِتات الزائدة لا تنتقل إلى المسار التالي. يُعمل على كل مسار بصورة منفصلة عن الآخر، حتى مع اشتراكها في السجل نفسه.
يضيف SIMD للأعداد الصحيحة عملية لا يملكها SIMD للفاصلة العائمة: الجمع والطرح المشبِعان، وهما يحصران القيمة بدل أن يلفّاها. تصبح النتيجة الواقعة فوق نطاق المسار أكبر قيمة يمكن للمسار حملها، وتصبح النتيجة الواقعة تحته أصغر قيمة.
تُدخِل صيغ التشبّع s (مُوقّع) أو us (غير مُوقّع) قبل لاحقة الحجم:
| التعليمة | المعنى |
|---|---|
paddsb |
add مشبِع لمسارات 8 بت مُوقّعة |
paddusb |
add مشبِع لمسارات 8 بت غير مُوقّعة |
psubsw |
sub مشبِع لمسارات 16 بت مُوقّعة |
psubusw |
sub مشبِع لمسارات 16 بت غير مُوقّعة |
نطاق الحصر هو كامل النطاق القابل للتمثيل لعدد صحيح بالحجم والإشارة الموافقين. بالنسبة إلى البايت:
[0, 255]: فـpaddusb للقيمة 200 + 100 يعطي 255، وpsubusb للقيمة 5 - 10 يعطي 0.[-128, 127]: فـpaddsb للقيمة 100 + 50 يعطي 127.يهمّ التشبّع عندما يحمل المسار كمية محدودة، مثل قناة بكسل أو عيّنة صوتية. فالالتفاف سيحوّل بكسلًا شديد السطوع إلى داكن، بينما يبقيه الحصر عند أقصى سطوع، وهو النتيجة التي تريدها.
لا توجد عمليتا الجمع والطرح المشبِعتان إلا لمسارات البايت والكلمة، وليس لمسارات dword أو qword.
قد ينتج عن ضرب قيمتين بحجم N بت حاصل بحجم 2N بت، لكن المسار الهدف لا يتّسع إلا لـN بت. وتحل عمليات SIMD ذلك بتحديد أي نصف من الحاصل يُحتفظ به، إما أدنى N بت أو أعلى N بت.
بالنسبة إلى مسارات 16 بت، تُستخدم ثلاث تعليمات:
| التعليمة | المعنى |
|---|---|
pmullw |
mul لمسارات 16 بت، مع الاحتفاظ بأدنى 16 بت من كل حاصل |
pmulhw |
mul لمسارات 16 بت، مع الاحتفاظ بأعلى 16 بت، بمعاملات مُوقّعة |
pmulhuw |
mul لمسارات 16 بت، مع الاحتفاظ بأعلى 16 بت، بمعاملات غير مُوقّعة |
أدنى 16 بت من الحاصل تبقى نفسها سواء قُرئت المُعاملات كمُوقّعة أم غير مُوقّعة، لذا توجد تعليمة pmullw واحدة فقط.
أما أعلى 16 بت فتختلف حسب إشارة النتيجة. ولهذا يكون لضرب النصف الأعلى صيغتان منفصلتان للضرب المُوقّع وغير المُوقّع:
pmulhw، للضرب المُوقّع.pmulhuw، بحرف u إضافي، للضرب غير المُوقّع.لاحظ الصياغة:
p، للأعداد الصحيحة المعبّأة.mul.h، للدلالة على اختيار البِتات العليا («العالية») من النتيجة.u اختياري إذا كان ينبغي تفسير النتيجة على أنها غير مُوقّعة (أي أنها غير موسّعة بالإشارة).w، للدلالة على أنها عملية على كلمة (16 بت).يتبع الضرب المعبّأ للكلمات القاعدة أعلاه تمامًا.
ويتبع ضرب dword (32 بت) القاعدة أيضًا، لكن لا توجد إلا النسخة التي تختار النصف الأدنى: pmulld.
لا توجد نسخة لضرب dword تختار البِتات العليا. ومع ذلك، توجد نسخ توسّع الضرب، فتخزّن الحاصل الكامل بـ64 بت للمسارات ذات الفهرس الزوجي (أي المسارين في الموضعين 0 و2):
| التعليمة | المعنى |
|---|---|
pmuludq |
ضرب العناصر ذات الفهرس الزوجي بحجم 32 بت، غير مُوقّعة، إلى حاصلين كاملين بحجم 64 بت |
pmuldq |
ضرب العناصر ذات الفهرس الزوجي بحجم 32 بت، مُوقّعة، إلى حاصلين كاملين بحجم 64 بت |
لاحظ أن الصياغة تستخدم 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 المعبّأة بين أعداد الفاصلة العائمة أحادية الدقة ومزدوجة الدقة.
توجد تعليمات للتحويل بين الأعداد الصحيحة المُوقّعة بحجم 32 بت ومسارات الفاصلة العائمة.
وهي تتبع الصياغة المعتادة لتعليمات 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، التي تجمع مسارين معًا، عينة بعينة.
كل مسار هو كتلة من 8 عينات موقّعة بطول 16 بت.
وأي مجموع يتجاوز نطاق الأعداد الموقّعة بطول 16 بت يجب أن يُثبَّت عند حدّ النطاق بدلًا من أن يلتفّ حوله.
تتلقّى هذه الدالة الوسائط التالية، بهذا الترتيب:
result: عنوان ذاكرة بمحاذاة 16 بايت لمخزن مؤقت تُكتب فيه العينات الثماني الممزوجة.track_a: عنوان ذاكرة بمحاذاة 16 بايت للمسار الأول، ويحوي 8 عينات موقّعة بطول 16 بت.track_b: عنوان ذاكرة بمحاذاة 16 بايت للمسار الثاني، ويحوي 8 عينات موقّعة بطول 16 بت.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، التي تطرح كتلة التسرّب من كتلة المسار، عينة بعينة.
وتتكوّن كل كتلة من 8 عينات موقّعة بطول 16 بت، ويجب أن يتشبّع الطرح.
تتلقّى هذه الدالة الوسائط التالية، بهذا الترتيب:
result: عنوان الذاكرة الذي تُكتب فيه العينات الثماني المنقّاة (المحاذاة غير مضمونة).track: عنوان ذاكرة بمحاذاة 16 بايت للمسار المسجَّل، وهو 8 عينات موقّعة بطول 16 بت.bleed: عنوان ذاكرة التسرّب المراد إزالته، وهو 8 عينات موقّعة بطول 16 بت (المحاذاة غير مضمونة).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: عنوان ذاكرة بمحاذاة 16 بايت تُكتب فيه القيم الست عشرة المجموعة.meter_a: عنوان ذاكرة بمحاذاة 16 بايت لكتلة مقياس القناة الأولى، وتضم 16 قيمة غير موقّعة بطول 8 بت محزومة.meter_b: عنوان ذاكرة بمحاذاة 16 بايت لكتلة مقياس القناة الثانية، وتضم 16 قيمة غير موقّعة بطول 8 بت محزومة.تتكوّن كل كتلة من 16 قيمة غير موقّعة بطول 8 بت. ويجب جمعها معًا، مع التشبّع ضمن النطاق غير الموقّع.
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: عنوان ذاكرة بمحاذاة 16 بايت تُكتب فيه العينات المحجَّمة كلها.track: عنوان ذاكرة بمحاذاة 16 بايت للمسار، ويحوي عددًا n من العينات الموقّعة بطول 16 بت.gains: عنوان ذاكرة بمحاذاة 16 بايت لمعاملات الكسب، وتضم عددًا n من القيم الموقّعة بطول 16 بت، كلٌّ منها في النطاق من 0 إلى 32767.n: عدد العينات، وهو دائمًا غير صفري ومضاعف للعدد 8، كعدد صحيح غير موقّع بطول 64 بت.لاحظ أن المسار مصفوفة تضم عددًا n من العينات الموقّعة بطول 16 بت، حيث n لا يكون صفرًا أبدًا ويكون دائمًا مضاعفًا للعدد 8.
ولكل عينة، تكون النتيجة المحجَّمة هي أعلى 16 بت من ناتج الضرب الموقّع: 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: عنوان ذاكرة بمحاذاة 16 بايت تُكتب فيه النتائج، كأعداد صحيحة موقّعة بطول 32 بت.samples: عنوان ذاكرة بمحاذاة 16 بايت للعينات، وهي عدد n من القيم الموقّعة بطول 16 بت.divisor: عنوان ذاكرة بمحاذاة 16 بايت لأربع نسخ متطابقة من المقسوم عليه، كأعداد صحيحة بطول 32 بت.n هو عدد العينات، وهو دائمًا غير صفري ومضاعف للعدد 4، كعدد صحيح غير موقّع بطول 64 بت.لاحظ أن samples مصفوفة تضم عددًا n من العينات الموقّعة بطول 16 بت، حيث n لا يكون صفرًا أبدًا ويكون دائمًا مضاعفًا للعدد 4.
يُقدَّم المقسوم عليه في صورة 4 نسخ من العدد الصحيح نفسه بطول 32 بت، مكرّرة سلفًا بحيث تملأ الخانات الأربع كلها دفعة واحدة.
لا تتوفّر في SIMD الصحيحة عملية قسمة محزومة، لذا تُنجَز العملية بالفاصلة العائمة المحزومة، ثم تُحوَّل مرة أخرى إلى أعداد صحيحة محزومة بطول 32 بت لتُخزَّن في 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 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.