وحدة المزج

وحدة المزج

تمرين تعلّمي

مقدمة

SIMD: الأعداد الصحيحة

قدّم مفهوم سابق قيم الفاصلة العائمة المعبّأة: أعداد متعددة محفوظة في سجل xmm واحد، وتُعالَج على مستوى المسار وعلى التوازي. ويمكن للسجلات xmm نفسها أن تحمل أيضًا أعدادًا صحيحة معبّأة.

الصياغة

ينتقل معظم نموذج SIMD الخاص بالفاصلة العائمة دون تغيير إلى الأعداد الصحيحة المعبّأة:

  • يُقسَّم السجل ذو 128 بت إلى مسارات
  • تعمل التعليمات على المسارات الواقعة في الموضع نفسه على التوازي
  • تتبع معاملات الذاكرة قواعد المحاذاة نفسها البالغة 16 بايت

لكن الصياغة تختلف قليلًا:

  1. بادئة p للدلالة على أن التعليمة تعمل على بيانات معبّأة.
  2. العملية المنفَّذة، بالاسم نفسه الذي يحمله نظيرها خارج SIMD (المسمّى أيضًا قياسيًا) مثل add وmul وغيرها.
  3. لاحقة للدلالة على حجم كل مسار.

للأعداد الصحيحة المعبّأة أربعة أحجام للمسارات، ولكل منها لاحقتها الخاصة:

عرض المسار البايتات المسارات في 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 بت دون أن تفسّرها. ويُستخدم الزوج المسمّى بأسماء الأعداد الصحيحة مع بيانات الأعداد الصحيحة بحكم العرف، لا بحكم الإلزام.

Note

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.

يهمّ التشبّع عندما يحمل المسار كمية محدودة، مثل قناة بكسل أو عيّنة صوتية. فالالتفاف سيحوّل بكسلًا شديد السطوع إلى داكن، بينما يبقيه الحصر عند أقصى سطوع، وهو النتيجة التي تريدها.

Note

لا توجد عمليتا الجمع والطرح المشبِعتان إلا لمسارات البايت والكلمة، وليس لمسارات dword أو qword.

الضرب

قد ينتج عن ضرب قيمتين بحجم N بت حاصل بحجم 2N بت، لكن المسار الهدف لا يتّسع إلا لـN بت. وتحل عمليات SIMD ذلك بتحديد أي نصف من الحاصل يُحتفظ به، إما أدنى N بت أو أعلى N بت.

بالنسبة إلى مسارات 16 بت، تُستخدم ثلاث تعليمات:

التعليمة المعنى
pmullw mul لمسارات 16 بت، مع الاحتفاظ بأدنى 16 بت من كل حاصل
pmulhw mul لمسارات 16 بت، مع الاحتفاظ بأعلى 16 بت، بمعاملات مُوقّعة
pmulhuw mul لمسارات 16 بت، مع الاحتفاظ بأعلى 16 بت، بمعاملات غير مُوقّعة

أدنى 16 بت من الحاصل تبقى نفسها سواء قُرئت المُعاملات كمُوقّعة أم غير مُوقّعة، لذا توجد تعليمة pmullw واحدة فقط.

أما أعلى 16 بت فتختلف حسب إشارة النتيجة. ولهذا يكون لضرب النصف الأعلى صيغتان منفصلتان للضرب المُوقّع وغير المُوقّع:

  1. pmulhw، للضرب المُوقّع.
  2. pmulhuw، بحرف u إضافي، للضرب غير المُوقّع.

لاحظ الصياغة:

  1. حرف p، للأعداد الصحيحة المعبّأة.
  2. العملية المنفَّذة، mul.
  3. حرف h، للدلالة على اختيار البِتات العليا («العالية») من النتيجة.
  4. حرف u اختياري إذا كان ينبغي تفسير النتيجة على أنها غير مُوقّعة (أي أنها غير موسّعة بالإشارة).
  5. وأخيرًا لاحقة الحجم 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
Caution

التعليمات التي تستخدم 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

التعليمات

أنت تكتب النواة الرقمية لمازج صوتي صغير في استوديو تسجيل.

يصل الصوت في هيئة تدفّق من العينات. وكل عينة عدد يصف موضع مخروط مكبّر الصوت في لحظة واحدة. ويعالج المازج كتلة كاملة من العينات دفعة واحدة، بتطبيق العملية نفسها على كل عينة.

أمامك خمس مهام، تعمل كل واحدة منها على كتلة عينات محفوظة في الذاكرة. وتتلقّى القيم التي تعمل عليها عبر عناوين الذاكرة، وتكتب إجابتك عبر عنوان النتيجة.

Note

يجب إجراء الحسابات في هذا التمرين باستخدام تعليمات SIMD، وليس عمليات قياسية.

1. امزج مسارين

يسجّل الاستوديو أحيانًا الآلات على مسارات مختلفة. ولتشغيلها عبر مكبرات الصوت نفسها، تجمع المسارين في مسار واحد.

نفّذ الدالة 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}

لا تُرجع هذه الدالة أي قيمة.

2. أزل التسرّب

عندما يسجّل ميكروفونان في الوقت نفسه، يلتقط كلٌّ منهما نسخة خافتة من مصدر الآخر. تُسمّى هذه النسخة غير المرغوبة التسرّب. وبعد قياس هذا التسرّب، يجب طرحه مرة أخرى لتنظيف المسار.

نفّذ الدالة 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}

لا تُرجع هذه الدالة أي قيمة.

3. اجمع المقاييس

فوق كل قناة يوجد مقياس مستوى، وهو شريط صغير تُظهر إضاءته مدى ارتفاع صوت تلك القناة. وفي اللوحة الأمامية مقياس رئيسي واحد ينبغي أن يضيء بنفس سطوع القناتين معًا. وأنت تجمع كتلتَي المقياس لتغذيته.

لاحظ أن المقياس الساطع يبقى عند أقصى سطوع بدلًا من أن يلتفّ عائدًا إلى العتمة، لذا فالجمع متشبّع.

نفّذ الدالة 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}

لا تُرجع هذه الدالة أي قيمة.

4. طبّق تلاشيًا تدريجيًا

لا ينبغي أن تتوقف الأغنية فجأة، بل أن تتلاشى تدريجيًا في نهايتها. لديك كتلة من معاملات الكسب، معامل واحد لكل عينة، تتناقص من أهدأ إلى أهدأ.

كل معامل هو كسر مقامه 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}
Note

الإبقاء على النصف الأعلى يقرّب نحو اللانهاية السالبة. ولهذا تكون نتيجة تحجيم 20000 * 32767 هي 9999، بينما تكون نتيجة تحجيم -20000 * 32767 هي -10000 بدلًا من -9999.

لا تُرجع هذه الدالة أي قيمة.

5. خفّف مسارًا

سُجِّل عازف الطبول بصوت أعلى بكثير مما ينبغي. ولخفض المسار كله، تقسم كل عينة على ثابت.

نفّذ الدالة 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}

لا تُرجع هذه الدالة أي قيمة.

تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
x86-64 Assembly Exercism

مستعد لبدء وحدة المزج؟

سجّل في Exercism لتتعلّم وتتقن x86-64 Assembly عبر 22 مفهومًا130 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.