ट्रैक
/
x86-64 Assembly
x86-64 Assembly
/
अभ्यास
/
अंक तालिका
अंक तालिका

अंक तालिका

सीखने का अभ्यास

परिचय

SIMD: मास्क और शर्तें

स्केलर कोड किसी शर्त के आधार पर ब्रांच करने के लिए अलग-अलग इंस्ट्रक्शनों द्वारा सेट किए गए फ्लैग पर निर्भर करता है। लेकिन पैक्ड वैल्यू एक नहीं, बल्कि एक साथ कई वैल्यू को दर्शाती हैं। एक ही शर्त किसी एक लेन के लिए पूरी हो सकती है और किसी दूसरी के लिए नहीं।

इसी कारण SIMD कोड डिफाल्ट रूप से ब्रांचलेस होता है।

फ्लैग पर निर्भर करने के बजाय, पैक्ड तुलनाएँ आम तौर पर डेस्टिनेशन ऑपरेंड में एक मास्क बनाती हैं। तुलना हर लेन के लिए यह करती है: शर्त सही होने पर पूरी लेन एक से भर जाती है, और गलत होने पर पूरी लेन शून्य से। साइन्ड पूर्णांक की तरह पढ़ने पर, सही लेन -1 होती है और गलत लेन 0.

इसके बाद इस मास्क को बिटवाइज़ ऑपरेशनों के साथ जोड़कर चुनिंदा लेन छाँटी जा सकती हैं।

पैक्ड तुलनाएँ

एक स्केलर cmp इस अर्थ में जेनेरिक होता है कि वह एक साथ कई फ्लैग सेट करता है। इसके बाद कोई दूसरा इंस्ट्रक्शन उन फ्लैग का उपयोग करके या तो ब्रांच कर सकता है या गणना कर सकता है।

लेकिन चूँकि पैक्ड तुलना एक ही साथ शर्त जाँचती भी है और मास्क भी बनाती है, वह जेनेरिक नहीं होती। तुलना को ठीक वही शर्त बतानी पड़ती है जिसकी जाँच की जा रही है।

ऐसा करने के दो तरीके हैं:

  • पूर्णांक तुलनाओं को शर्त प्रत्यय (suffix) के रूप में दी जाती है: बराबरी के लिए 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 (और 64-बिट लेन के लिए cmppd), जिसमें शर्त इमीडिएट के रूप में दी जाती है:

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

NASM में ऐसे स्यूडो-ऑप भी हैं जो सही इमीडिएट पर मैप होते हैं और याद रखने में आसान हैं। नीचे दिए सभी में, px के x की जगह s (32-बिट फ्लोट) या d (64-बिट फ्लोट) हो सकता है:

स्यूडो-ऑप इमीडिएट तुलना
cmpeqpx 0 a == b
cmpltpx 1 a < b
cmplepx 2 a <= b
cmpunordpx 3 a या 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

ये सभी इंस्ट्रक्शन जिस ऑपरेशन को अंजाम देते हैं (blend) उसके बाद एक v जोड़ देते हैं। यह 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: उस बफर का मेमरी एड्रेस जहाँ मास्क की 4 लेन लिखी जाती हैं।
  • 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: उस बफर का मेमरी एड्रेस जहाँ मास्क की 4 लेन लिखी जाती हैं।
  • 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: उस बफर का मेमरी एड्रेस जहाँ 4 रैंक लिखी जाती हैं, हर रैंक एक 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 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।