ورقة الدرجات

ورقة الدرجات

تمرين تعلّمي

مقدمة

‏SIMD: الأقنعة والشروط

يعتمد الكود القياسي على علامات تضبطها تعليمات مختلفة للتفرّع استجابةً لشرط معيّن. لكن القيم المحزّمة لا تمثّل قيمة واحدة، بل قيمًا كثيرة بالتوازي. وقد يفشل شرط واحد في مسار وينجح في آخر.

لهذا يكون كود ‏SIMD بلا تفرّع افتراضيًا.

فبدلًا من الاعتماد على العلامات، تُنتج المقارنات المحزّمة عادةً قناعًا في معامل الوجهة. فلكل مسار، تملأ المقارنةُ المسارَ كله بآحاد عند تحقّق الشرط وبأصفار عند عدم تحقّقه. وعند قراءته عددًا صحيحًا موقّعًا، يكون المسار الصحيح -1 والمسار الخاطئ 0.

ويمكن بعد ذلك تركيب هذا القناع مع عمليات بتّية لتصفية مسارات محدّدة.

المقارنات المحزّمة

تُعتبر تعليمة cmp القياسية عامةً بمعنى أنها تُستخدم لضبط عدة علامات في الوقت نفسه. ثم تستهلك تعليمة أخرى تلك العلامات إما للتفرّع أو لإجراء حسابات.

لكن، بما أن المقارنة المحزّمة تتحقق من شرط وتحسب قناعًا في آن واحد، فهي ليست عامة. إذ يجب إعطاء المقارنة الشرط الدقيق الذي تُختبره.

وهناك طريقتان لفعل ذلك:

  • تُعطى مقارنات الأعداد الصحيحة الشرطَ كلاحقة: eq للتساوي، وgt لـ"أكبر من". وتُبنى الصيغ الأخرى بتركيب ناتج إحدى هاتين المقارنتين.
  • تُعطى مقارنات الفاصلة العائمة الشرطَ مُرمَّزًا في قيمة فورية. وتقابل قيم مختلفة لهذه القيمة الفورية شروطًا مختلفة تُختبر.

وبخلاف استخدام لاحقة شرطية محدّدة في مقارنات الأعداد الصحيحة، تتبع الصيغة البنية نفسها التي رأيناها سابقًا:

  • للأعداد الصحيحة، p + cmp + الشرط + الحجم (b أو w أو d أو q).
  • لأعداد الفاصلة العائمة، cmp + p + الحجم (s أو d). ويُمرَّر الشرط في قيمة فورية كمعامل إضافي.
مقارنات الأعداد الصحيحة

كما ذُكر، لا توجد مقارنات أعداد صحيحة إلا للتساوي و"أكبر من":

تعليمة وصف
pcmpeqb, pcmpeqw, pcmpeqd, pcmpeqq تساوٍ لكل مسار
pcmpgtb, pcmpgtw, pcmpgtd, pcmpgtq أكبر من موقّع لكل مسار
movdqa  xmm0, [rel scores]
pcmpgtd xmm0, [rel threshold] ; lane i = 0xFFFFFFFF (-1) if scores[i] > threshold[i], else 0

لإنشاء مقارنة "أصغر من"، استخدم gt مع تبديل المعاملين: a < b == b > a.

لاحظ أن المقارنة موقّعة. لإجراء مقارنة غير موقّعة، اقلب البت الأعلى في كلا المعاملين. ويمكن فعل ذلك عبر عملية XOR مع قناع يكون فيه البت الأعلى وحده مضبوطًا.

Note

ثمة أسلوبان مُفيدان هما:

  1. تطبيق XOR على سجل مع نفسه لإنتاج كلها أصفار.
  2. مقارنة سجل مع نفسه لإنتاج كلها آحاد.

على سبيل المثال:

pxor xmm4, xmm4    ; xmm4 = all zeros
pcmpeqd xmm7, xmm7 ; xmm7 = all ones

"كلها أصفار" و"كلها آحاد" قناعان شائعان لتشفير "خطأ في كل مكان" و"صحيح في كل مكان" على التوالي. ويمكن استخدامهما أيضًا لتمثيل 0 المحزّم أو -1 المحزّم، وهما من القيم الحارسة الشائعة. على سبيل المثال، محرف NUL الذي يعلّم نهاية سلسلة نصية هو 0.

مقارنات الفاصلة العائمة

تستخدم مسارات الفاصلة العائمة شكلًا مختلفًا: تعليمة واحدة، cmpps (وcmppd للمسارات ذات 64 بت)، مع الشرط كقيمة فورية:

movaps xmm0, [rel readings]
cmpps  xmm0, [rel limits], 1 ; condition 1 is "less than": lane i = all ones if readings[i] < limits[i]

يمتلك NASM أيضًا عمليات زائفة تُقابل القيمة الفورية الصحيحة ويسهل تذكّرها. في كل ما يلي، يمكن أن يكون x في px إما s (أعداد الفاصلة العائمة 32 بت) أو d (أعداد الفاصلة العائمة 64 بت):

عملية زائفة قيمة فورية مقارنة
cmpeqpx 0 a == b
cmpltpx 1 a < b
cmplepx 2 a <= b
cmpunordpx 3 a قيمة ‏NaN أو b قيمة ‏NaN
cmpneqpx 4 a != b
cmpnltpx 5 a >= b
cmpnlepx 6 a > b
cmpordpx 7 لا a ولا b قيمة ‏NaN

الاختيار باستخدام قناع

يُرمِّز القناع نتيجة شرط. ويمكن بعد ذلك استخدامه للاختيار، مسارًا بمسار، بين مجموعتين من القيم وفقًا لذلك الشرط. فنأخذ المسار من قيمة ما حيث يكون القناع صحيحًا، ومن قيمة أخرى حيث يكون خاطئًا:

; result = (a AND mask) OR (b AND NOT mask)
movdqa xmm2, xmm0  ; xmm0 holds the mask, keep a copy
pand   xmm2, xmm3  ; xmm2 = a AND mask: lanes of a where mask is true
pandn  xmm0, xmm4  ; xmm0 = NOT mask AND b: lanes of b where mask is false
por    xmm2, xmm0  ; combine the two halves

لاحظ أن اللاتماثل في pandn يُثمر هنا: فالقناع يستقر في الوجهة، ويُقلب، ويختار من b في تعليمة واحدة.

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

تعليمات المزج المخصّصة

توجد تعليمات تُجري ذلك الاختيار نفسه مباشرةً، فتقرأ بتة واحدة لكل عنصر من سجل قناع. وتُسمى تعليمات المزج:

تعليمة عنصر مصدر القناع
pblendvb بايت xmm0 الضمني
blendvps مسار 32 بت xmm0 الضمني
blendvpd مسار 64 بت xmm0 الضمني

لاحظ أن التعليمة الأولى تتبع صيغة الأعداد الصحيحة، بينما تتبع الأخريان صيغة الفاصلة العائمة. لكن، بما أن هذه التعليمات تختار ببساطة بايتات خام، يمكن استخدام أيٍّ منها مع الأعداد الصحيحة وأعداد الفاصلة العائمة معًا.

فلكل عنصر، يُبقي المزج الوجهة عندما يكون البت الأعلى لعنصر القناع المقابل صفرًا، ويأخذ المصدر عندما يكون مضبوطًا. ولا يُستشار إلا ذلك البت الأعلى، وهو ما يحققه قناع المقارنة، لأن مساراته كلها آحاد أو كلها أصفار. وسجل القناع دائمًا هو xmm0، وهو ضمني:

movaps   xmm0, [rel mask]  ; the selecting mask must be in xmm0
movaps   xmm1, [rel b]     ; destination: kept where the mask bit is clear
blendvps xmm1, [rel a]     ; source: taken where the mask bit is set

ومن الممكن أيضًا استخدام pblendvb لاختيار مسارات من قناع مقارنة لأي حجم آخر. وبما أن كل البايتات في المسار الصحيح هي آحاد، يختار pblendvb كلها.

Note

تضيف هذه التعليمات جميعًا v بعد العملية المُنفَّذة (blend). ويشير هذا v إلى variable، لأن الاختيار ليس ثابتًا: فهو يعتمد على سجل.

وتوجد أيضًا صيغ بدون v، تختار وفقًا لقيمة فورية. وهي تتبع النمط نفسه، فتختار المسار i إذا كان البت i في القيمة الفورية مضبوطًا.

العودة من القناع إلى القياسي

رغم قوته، يفتقر كود ‏SIMD إلى الكثير من مرونة الكود القياسي. وفي مواقف كثيرة، يلزم الانتقال من سجل محزّم عودةً إلى عالم التعليمات القياسية.

وتعمل عائلة التعليمات movmsk كجسر بين العالمين. تستخرج هذه التعليمات البت الأعلى من كل مسار إلى سجل الأغراض العامة:

تعليمة تجمع عرض النتيجة
pmovmskb البت الأعلى لكل من 16 بايت 16 بت
movmskps البت الأعلى لكل من 4 كلمات مزدوجة 4 بتات
movmskpd البت الأعلى لكل من 2 كلمات رباعية 2 بت

وإذا استُخدمت بعد مقارنة، مثّل كل بت مضبوط مسارًا "صحيحًا" وكل بت مصفّر مسارًا "خاطئًا". ويمكن بعد ذلك التعامل مع هذه النتيجة كالمعتاد بالتعليمات القياسية. فعلى سبيل المثال، يعدّ popcnt عدد التطابقات، ويجد tzcnt أولها.

وقد يكون سجل الأغراض العامة بعرض 32 بت أو 64 بت.

اختبار متجه كامل

توجد أيضًا صيغة محزّمة من تعليمة test القياسية: ptest.

وهي تشبه نظيرتها القياسية في أنها تُجري عملية AND بين معاملين، دون تعديلهما. وبخلاف test، تُجري ptest أيضًا عملية ANDN، فتقلب المعامل الأول.

وهكذا يمكن اعتبار ptest نسخة غير مُدمِّرة من pand وpandn تضبط العلامات وفقًا للنتيجة. وبالطريقة نفسها تقريبًا التي تتبعها هاتان التعليمتان، تتعامل ptest مع سجل ‏SIMD كله كمسار واحد، ولذلك لا تأخذ بادئة حجم.

فإذا كانت نتيجة عملية AND هي 0، ضُبطت ZF، وإذا أسفرت عملية ANDN عن 0، فإن المضبوطة هي CF. وهذا يعني أنه يمكن استخدام ptest للتحقق من قناع كلها آحاد وقناع كلها أصفار معًا:

  1. استخدام ptest على سجل مع نفسه يضبط ZF فقط إذا كان السجل كله أصفارًا. ويحاكي هذا الأسلوبَ القياسي الشائع المتمثل في استخدام test على سجل مع نفسه للتحقق من 0.
  2. واستخدام ptest على سجل مع قناع كلها آحاد يضبط CF فقط إذا كان السجل كله آحادًا. كما يضبط ZF فقط إذا كان السجل كله أصفارًا، مما يتيح التحقق من القناعين معًا.
pxor    xmm0, xmm0   ; all zeros
pcmpeqb xmm1, xmm1   ; all ones
pcmpeqb xmm2, xmm2

ptest xmm0, xmm0     ; ZF set: a register against itself detects all zeros
ptest xmm0, xmm1     ; ZF set, CF clear: xmm0 is all zeros, not all ones
ptest xmm2, xmm1     ; CF is set only if xmm2 is all ones

ويمكن استخدام نتيجة ptest للتفرّع أو في التعليمات بدون تفرّع مثل setcc أو cmovcc، كالمعتاد.

التعليمات

أنت تدير محطة التصحيح في مدرسة، وتقيّم نتائج الفصل كتلة تلو الأخرى.

تحتوي كل كتلة على 4 نتائج، وتطبّق المحطة العملية نفسها على كل نتيجة في الكتلة. الدرجة عدد عائم بعرض 32 بت. تعمل عدة خطوات باستخدام قناع: كتلة من 4 مسارات، حيث يكون كل مسار إما كله آحاد (أي نعم لتلك النتيجة) أو كله أصفار (أي لا).

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

جميع عناوين الذاكرة في هذا التمرين محاذاة على 16 بايت.

Note

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

1. علّم الدرجات التي تتجاوز العتبة

تُقيّم الخطوة الأولى كل نتيجة مقابل عتبة. تتجاوز النتيجة الحد عندما تكون درجتها أكبر تمامًا من العتبة. أما أي درجة أقل من العتبة أو مساوية لها فلا تتجاوزها.

نفّذ الدالة flag_above_threshold، التي تبني قناعًا فيه مسار كله آحاد لكل درجة تتجاوز عتبتها، ومسار كله أصفار فيما عدا ذلك.

تأخذ هذه الدالة الوسائط التالية، بهذا الترتيب:

  • result: عنوان ذاكرة لمخزن مؤقت تُكتب فيه مسارات القناع الأربعة.
  • scores: عنوان ذاكرة الدرجات، ويضم 4 أعداد عائمة عادية بعرض 32 بت (لا تكون أبدًا NaN).
  • thresholds: عنوان ذاكرة عتبة كل مسار، ويضم 4 أعداد عائمة عادية بعرض 32 بت (لا تكون أبدًا NaN).
scores     = {72.0, 55.0, 90.0, 40.0}
thresholds = {60.0, 60.0, 60.0, 60.0}
result     = {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}

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

2. علّم الدرجات الكاملة

يُبرز تقرير منفصل النتائج الكاملة، تلك التي بلغت العلامة القصوى الممكنة.

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

تأخذ هذه الدالة الوسائط التالية، بهذا الترتيب:

  • result: عنوان ذاكرة لمخزن مؤقت تُكتب فيه مسارات القناع الأربعة.
  • scores: عنوان ذاكرة الدرجات، ويضم 4 أعداد عائمة عادية بعرض 32 بت (لا تكون أبدًا NaN).
  • maxima: عنوان ذاكرة العلامة القصوى لكل مسار، ويضم 4 أعداد عائمة عادية بعرض 32 بت (لا تكون أبدًا NaN).
scores = {100.0, 88.0, 100.0, 73.0}
maxima = {100.0, 100.0, 100.0, 100.0}
result = {0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000}

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

3. عيّن رتبة

تحصل كل درجة على رتبة من 1 إلى 3:

  • الرتبة 1 لدرجة عند عتبة النجاح 50.0 أو أقل منها.
  • الرتبة 2 لدرجة تتجاوز تلك العتبة لكنها دون الحد الأقصى.
  • الرتبة 3 للدرجة الكاملة، التي تساوي الحد الأقصى.

نفّذ الدالة assign_ranks، التي تكتب رتبة كل درجة.

ينبغي أن تعرّف عتبة النجاح وقيم الرتب كثوابت محزومة في الذاكرة. يمكن إعادة استخدام الدالتين في المهمتين السابقتين: تكون الدرجة من الرتبة 2 على الأقل عندما تتجاوز العتبة، ومن الرتبة 3 عندما تساوي الحد الأقصى.

تأخذ هذه الدالة الوسائط التالية، بهذا الترتيب:

  • result: عنوان ذاكرة لمخزن مؤقت تُكتب فيه الرتب الأربع، وكل رتبة عدد صحيح بدون إشارة بعرض 32 بت.
  • scores: عنوان ذاكرة الدرجات، ويضم 4 أعداد عائمة عادية بعرض 32 بت (لا تكون أبدًا NaN).
  • maxima: عنوان ذاكرة العلامة القصوى لكل مسار، ويضم 4 أعداد عائمة عادية بعرض 32 بت (لا تكون أبدًا NaN).
scores = {40.0, 75.0, 100.0, 60.0}
maxima = {100.0, 100.0, 100.0, 100.0}
result = {1, 2, 3, 2}

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

4. أحصِ حالات الرسوب

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

نفّذ الدالة count_failures، التي تُرجع عدد الرتب، في كل كتلة، التي تقع تمامًا دون عتبة النجاح. تُعطى العتبة ككتلة من 4 مسارات متطابقة، بحيث يمكنك تحميلها مرة واحدة وإعادة استخدامها مع كل كتلة.

تأخذ هذه الدالة الوسائط التالية، بهذا الترتيب:

  • ranks: عنوان ذاكرة الرتب، بعدد صحيح من كتل ذات 4 مسارات، وكل رتبة عدد صحيح بدون إشارة بعرض 32 بت.
  • block_count: عدد الكتل ذات 4 مسارات، وهو دائمًا أكبر من 0.
  • pass_threshold: عنوان ذاكرة عتبة النجاح، ويضم 4 أعداد صحيحة متطابقة بعرض 32 بت.
ranks          = {1, 2, 3, 1, 2, 2, 1, 3} // 2 blocks
block_count    = 2
pass_threshold = {2, 2, 2, 2}
// => 3

تُرجع هذه الدالة العدد كعدد صحيح بإشارة بعرض 32 بت.

5. هل نجح الجميع؟

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

نفّذ الدالة all_passed، التي تُرجع 1 إذا نجح جميع الطلاب، و0 فيما عدا ذلك. ينجح الطالب عندما يكون مساره المقابل في مصفوفة failing كله أصفار.

تأخذ هذه الدالة الوسائط التالية، بهذا الترتيب:

  • failing: عنوان ذاكرة أقنعة الرسوب، بعدد صحيح من كتل ذات 4 مسارات، وكل مسار إما كله آحاد أو كله أصفار.
  • block_count: عدد الكتل ذات 4 مسارات، وهو دائمًا أكبر من 0.
failing     = {0x00000000, 0x00000000, 0x00000000, 0x00000000,
               0x00000000, 0x00000000, 0x00000000, 0x00000000} // 2 blocks
block_count = 2
// => 1

تُرجع هذه الدالة الإجابة كعدد صحيح بإشارة بعرض 32 بت، إما 1 أو 0.

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

مستعد لبدء ورقة الدرجات؟

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