पिछले कॉन्सेप्ट में पैक्ड फ्लोटिंग-पॉइंट वैल्यू का परिचय दिया गया था: एक ही xmm रजिस्टर में रखी कई संख्याएँ, जिन पर लेन के अनुसार और समानांतर में काम होता है। इन्हीं xmm रजिस्टरों में पैक्ड पूर्णांक भी रखे जा सकते हैं।
SIMD के फ्लोटिंग-पॉइंट मॉडल का ज़्यादातर हिस्सा पैक्ड पूर्णांकों पर भी बिना किसी बदलाव के लागू होता है:
लेकिन सिंटैक्स थोड़ा अलग है:
p, जो बताता है कि निर्देश पैक्ड डेटा पर काम करता है।add, mul आदि)।पैक्ड पूर्णांकों में लेन के चार आकार होते हैं, और हर आकार का अपना प्रत्यय होता है:
| लेन की चौड़ाई | बाइट | 128 बिट में लेन | प्रत्यय |
|---|---|---|---|
| बाइट | 1 | 16 | b |
| वर्ड | 2 | 8 | w |
| डीवर्ड | 4 | 4 | d |
| क्यूवर्ड | 8 | 2 | q |
उदाहरण के लिए:
| निर्देश | अर्थ |
|---|---|
paddb |
पैक्ड add, 8-बिट लेन (16 लेन) |
paddw |
पैक्ड add, 16-बिट लेन (8 लेन) |
paddd |
पैक्ड add, 32-बिट लेन (4 लेन) |
paddq |
पैक्ड add, 64-बिट लेन (2 लेन) |
कुछ निर्देश इनपुट में एक आकार की लेन लेते हैं, लेकिन आउटपुट में दूसरे आकार की। ये भी वही सामान्य नियम मानते हैं, लेकिन आकार के दो प्रत्ययों के साथ। पहला इनपुट लेन का आकार बताता है, दूसरा आउटपुट लेन का आकार:
| निर्देश | अर्थ |
|---|---|
pmovsxwd |
पैक्ड movsx, 16-बिट लेन से 32-बिट लेन तक |
pmuldq |
पैक्ड mul, 32-बिट लेन से 64-बिट लेन तक |
पूर्णांक वाले नाम के दो निर्देश xmm रजिस्टर और मेमोरी के बीच 128 बिट कॉपी करते हैं:
| निर्देश | विवरण |
|---|---|
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 के मॉड्यूलो में रखती है, इसलिए 200 + 100 का paddb 300 - 256 = 44 देता है और जो बिट फिट नहीं होते, उन्हें छोड़ देता है।
ध्यान दीजिए कि वे अतिरिक्त बिट अगली लेन में नहीं जाते। हर लेन पर दूसरी लेन से अलग, अकेले काम होता है, भले ही वे एक ही रजिस्टर साझा करती हैं।
पूर्णांक SIMD में एक ऐसा ऑपरेशन जुड़ता है जो फ्लोटिंग-पॉइंट SIMD में नहीं होता: संतृप्त जोड़ और घटाव, जो रैप होने की जगह क्लैंप करते हैं। लेन की रेंज से ऊपर का परिणाम उस लेन की सबसे बड़ी वैल्यू बन जाता है; रेंज से नीचे का परिणाम सबसे छोटी वैल्यू बन जाता है।
संतृप्त रूप आकार के प्रत्यय से पहले s (चिह्नित) या us (अचिह्नित) जोड़ते हैं:
| निर्देश | अर्थ |
|---|---|
paddsb |
add, संतृप्त, चिह्नित 8-बिट लेन |
paddusb |
add, संतृप्त, अचिह्नित 8-बिट लेन |
psubsw |
sub, संतृप्त, चिह्नित 16-बिट लेन |
psubusw |
sub, संतृप्त, अचिह्नित 16-बिट लेन |
क्लैंप की रेंज उसी आकार और चिह्न वाले पूर्णांक की दर्शाई जा सकने वाली पूरी रेंज होती है। एक बाइट के लिए:
[0, 255] पर क्लैंप होते हैं: 200 + 100 का paddusb 255 देता है, और 5 - 10 का psubusb 0 देता है।[-128, 127] पर क्लैंप होते हैं: 100 + 50 का paddsb 127 देता है।संतृप्ति तब मायने रखती है जब किसी लेन में कोई सीमित मात्रा रखी जाती हो, जैसे पिक्सल का कोई चैनल या ऑडियो का कोई सैंपल। रैप अराउंड बहुत तेज़ चमक वाले पिक्सल को काला कर देता, जबकि क्लैंप करने से वह अधिकतम चमक पर बना रहता है, और यही नतीजा आपको चाहिए।
संतृप्त जोड़ और घटाव सिर्फ बाइट और वर्ड लेन के लिए हैं, डीवर्ड या क्यूवर्ड के लिए नहीं।
दो 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, जो बताता है कि परिणाम के ऊपरी ("high") बिट चुने जा रहे हैं।u (यानी उसमें चिह्न का विस्तार नहीं होता)।w, जो बताता है कि यह वर्ड (16-बिट) ऑपरेशन है।वर्ड के लिए पैक्ड गुणा ऊपर बताया गया नियम ठीक वैसे ही मानता है। डीवर्ड (32-बिट) गुणा भी यही नियम मानता है, लेकिन उसमें सिर्फ नीचे का आधा हिस्सा चुनने वाला रूप है: pmulld।
डीवर्ड गुणा में ऊपर के बिट चुनने वाला कोई रूप नहीं है। लेकिन कुछ रूप गुणा को चौड़ा कर देते हैं और उन लेन का पूरा 64-बिट गुणनफल रखते हैं जिनका इंडेक्स सम होता है (यानी स्थान 0 और 2 वाली लेन):
| निर्देश | अर्थ |
|---|---|
pmuludq |
सम-इंडेक्स वाले 32-बिट एलिमेंट का अचिह्नित गुणा करके 2 पूरे 64-बिट गुणनफल देना |
pmuldq |
सम-इंडेक्स वाले 32-बिट एलिमेंट का चिह्नित गुणा करके 2 पूरे 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-बाइट अलाइन्ड मेमोरी एड्रेस, जहाँ 8 मिक्स किए गए सैंपल लिखे जाते हैं।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: वह मेमोरी एड्रेस जहाँ 8 साफ़ किए गए सैंपल लिखे जाते हैं (अलाइनमेंट की गारंटी नहीं है)।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-बाइट अलाइन्ड मेमोरी एड्रेस जहाँ 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 स्केल होकर -9999 के बजाय -10000 बनता है।
इस फंक्शन का कोई रिटर्न वैल्यू नहीं है।
ड्रमर को बहुत ज़्यादा तेज़ रिकॉर्ड किया गया था। पूरे ट्रैक का लेवल कम करने के लिए, आप हर सैंपल को एक स्थिरांक से भाग देते हैं।
attenuate_track फंक्शन बनाइए।
यह फंक्शन इस क्रम में ये आर्गुमेंट लेता है:
result: वह 16-बाइट अलाइन्ड मेमोरी एड्रेस जहाँ परिणाम 32-बिट साइन्ड पूर्णांक के रूप में लिखे जाते हैं।samples: सैंपल का 16-बाइट अलाइन्ड मेमोरी एड्रेस, n साइन्ड 16-बिट वैल्यू।divisor: भाजक की 4 समान प्रतियों का 16-बाइट अलाइन्ड मेमोरी एड्रेस, 32-बिट पूर्णांक के रूप में।n: सैंपल की संख्या, जो हमेशा शून्य नहीं होती और 4 का गुणज होती है, एक 64-बिट अनसाइन्ड पूर्णांक के रूप में।ध्यान दीजिए कि samples एक ऐरे है जिसमें n साइन्ड 16-बिट सैंपल रहते हैं, जहाँ n कभी शून्य नहीं होता और हमेशा 4 का गुणज होता है।
भाजक को उसी 32-बिट पूर्णांक की 4 प्रतियों के रूप में दिया जाता है, जो पहले से दोहराई गई हैं ताकि वह एक साथ चारों लेन भर सके।
पूर्णांक SIMD में पैक्ड भाग नहीं होता, इसलिए यह काम पैक्ड फ्लोटिंग-पॉइंट में किया जाता है, और फिर result में रखने के लिए वापस पैक्ड 32-बिट पूर्णांक में बदला जाता है।
शून्य की ओर ट्रंकेट करके वापस बदलिए, इसलिए 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 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।