ट्रैक
/
x86-64 Assembly
x86-64 Assembly
/
अभ्यास
/
उत्पादन लाइन
उत्पादन लाइन

उत्पादन लाइन

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

परिचय

SIMD

सिंगल इंस्ट्रक्शन, मल्टीपल डेटा (SIMD) हार्डवेयर के स्तर पर समानांतरता का एक रूप है। इसमें CPU एक ही गणना कई एलिमेंट पर एक साथ करता है।

उदाहरण के लिए, मान लीजिए आपको 32-बिट दशमलव संख्याओं वाले चार एलिमेंट के दो ऐरे जोड़ने हैं, एक-एक एलिमेंट को जोड़ते हुए। स्केलर तरीके में चार अलग-अलग addss निर्देश चलाने पड़ते हैं, हर एलिमेंट के लिए एक। SIMD तरीके में एक ही addps से चारों जोड़ एक साथ हो जाते हैं। इस तरह एक निर्देश में चार गुना काम हो जाता है।

x86-64 असेंबली में SIMD निर्देश आमतौर पर xmm रजिस्टरों की मदद से चलाए जाते हैं। इनमें से कुछ निर्देश पूर्णांकों पर काम करते हैं। कुछ दशमलव संख्याओं पर काम करते हैं।

इन पूर्णांकों या दशमलव संख्याओं को पैक्ड कहा जाता है। इनमें से हर एक उसी रजिस्टर के अंदर एक जगह घेरता है, जिसे लेन कहते हैं। SIMD निर्देश आमतौर पर दो स्रोत ऑपरेंडों में एक ही लेन स्थिति पर रखी वैल्यू पर एक साथ काम करते हैं। हर ऑपरेशन का परिणाम गंतव्य ऑपरेंड में उसी स्थिति वाली लेन में संग्रहीत होता है।

लेन कोई कठोर सीमा नहीं है। यह तो हर निर्देश द्वारा बाइटों के एक क्रम को दी गई एक व्याख्या है। एक 128-बिट ऑपरेंड में 16 बाइट होते हैं। निर्देश के अनुसार इन बाइटों को 16 1-बाइट लेन, 8 2-बाइट लेन, 4 4-बाइट लेन, 2 8-बाइट लेन, या एक ही 16-बाइट लेन के रूप में पढ़ा जा सकता है।

उदाहरण के लिए, addps निर्देश पैक्ड सिंगल-प्रिसिज़न (4-बाइट) दशमलव संख्याएँ जोड़ता है। इस निर्देश के लिए ऑपरेंड का हर 4-बाइट हिस्सा एक अलग लेन होता है। 128-बिट चौड़े (16-बाइट) ऑपरेंड में:

ऑपरेंड चौथी लेन तीसरी लेन दूसरी लेन पहली लेन
स्रोत 1 1.5 2.1 6.2 3.4
+ + + + +
स्रोत 2 0.5 8.26 -13.4 22.9
= = = = =
परिणाम 2.0 10.36 -7.2 26.3

दूसरी ओर, mulpd के मामले में एक 128-बिट ऑपरेंड में 2 लेन मानी जाएँगी। ऐसा इसलिए है क्योंकि यह निर्देश डबल-प्रिसिज़न (8-बाइट) संख्याओं पर पैक्ड गुणा करता है:

ऑपरेंड दूसरी लेन पहली लेन
स्रोत 1 4.2 2.1
* * *
स्रोत 2 1.0 92.4
= = =
परिणाम 4.2 194.04

इस कॉन्सेप्ट में हम SIMD निर्देशों के एक छोटे से चुनिंदा समूह पर ध्यान देंगे। ये xmm रजिस्टरों का इस्तेमाल करते हुए पैक्ड दशमलव संख्याओं पर काम करते हैं।

दशमलव संख्याओं वाले SIMD निर्देश

मोटे तौर पर, दशमलव संख्याओं पर काम करने वाले SIMD निर्देशों का रूप दो ऑपरेंड वाला होता है। इनके नाम एक तय ढाँचे का पालन करते हैं:

  1. जो ऑपरेशन किया जा रहा है, उसका वही नाम जो उसके नॉन-SIMD (जिसे स्केलर भी कहते हैं) रूप का होता है (जैसे add, mul, आदि)।
  2. एक प्रत्यय p, जो बताता है कि निर्देश पैक्ड डेटा पर काम करता है, ठीक उसी जगह जहाँ पहले s (स्केलर डेटा के लिए) होता था।
  3. एक प्रत्यय, जो हर लेन का आकार बताता है और उसी अर्थ का पालन करता है जो उसके नॉन-SIMD रूप का होता है (s सिंगल-प्रिसिज़न के लिए और d डबल-प्रिसिज़न के लिए)।

इसलिए पैक्ड नाम स्केलर नाम से इस तरह बनता है कि निमोनिक के बीच वाले s (स्केलर) की जगह p (पैक्ड) लगा दिया जाता है। ऑपरेशन का नाम और आकार वाला प्रत्यय वैसे ही रहते हैं:

स्केलर पैक्ड अर्थ
addss addps जोड़, सिंगल-प्रिसिज़न (32-बिट लेन)
addsd addpd जोड़, डबल-प्रिसिज़न (64-बिट लेन)
mulss mulps गुणा, सिंगल-प्रिसिज़न
mulsd mulpd गुणा, डबल-प्रिसिज़न
अंकगणित

अंकगणित वाले SIMD निर्देश ऊपर बताए नियम का पालन करते हैं। कुछ उदाहरण:

addps xmm0, xmm1 ; xmm0 = xmm0 + xmm1 (each 32-bit float at the same position)
subpd xmm2, xmm3 ; xmm2 = xmm2 - xmm3 (each 64-bit float at the same position)
mulps xmm4, xmm5 ; xmm4 = xmm4 * xmm5 (each 32-bit float at the same position)
divpd xmm6, xmm7 ; xmm6 = xmm6 / xmm7 (each 64-bit float at the same position)
प्रिसिज़न के बीच रूपांतरण

पैक्ड सिंगल-प्रिसिज़न और पैक्ड डबल-प्रिसिज़न दशमलव संख्याओं के बीच रूपांतरित करने के लिए भी SIMD निर्देश होते हैं। ये भी वही नामकरण नियम अपनाते हैं, जिसमें स्केलर वाले हर s की जगह पैक्ड वाला p आ जाता है:

स्केलर पैक्ड अर्थ
cvtss2sd cvtps2pd सिंगल-प्रिसिज़न को डबल-प्रिसिज़न में रूपांतरित करना
cvtsd2ss cvtpd2ps डबल-प्रिसिज़न को सिंगल-प्रिसिज़न में रूपांतरित करना

ये निर्देश उस पहले बताई गई बात को साफ़ करते हैं कि लेन एक व्याख्या है, कोई कठोर सीमा नहीं। स्रोत और गंतव्य ऑपरेंडों की लेन का आकार अलग होता है, इसलिए उनकी लेन की संख्या भी अलग होती है।

cvtps2pd में स्रोत (ps) में 4 सिंगल-प्रिसिज़न लेन होती हैं। गंतव्य (pd) में 2 डबल-प्रिसिज़न लेन होती हैं। स्रोत की सिर्फ नीचे वाली 2 लेन पढ़ी जाती हैं। ऊपर वाली 2 को अनदेखा कर दिया जाता है:

ऑपरेंड चौथी लेन तीसरी लेन दूसरी लेन पहली लेन
ps के रूप में स्रोत अनदेखा अनदेखा 2.5 1.5
pd के रूप में गंतव्य 2.5 1.5

cvtpd2ps में स्रोत (pd) में 2 डबल-प्रिसिज़न लेन होती हैं। गंतव्य (ps) में 4 सिंगल-प्रिसिज़न लेन होती हैं। स्रोत की दोनों लेन रूपांतरित होकर गंतव्य की नीचे वाली 2 लेन में लिखी जाती हैं। गंतव्य की ऊपर वाली 2 लेन शून्य कर दी जाती हैं:

ऑपरेंड चौथी लेन तीसरी लेन दूसरी लेन पहली लेन
pd के रूप में स्रोत 4.0 3.0
ps के रूप में गंतव्य 0 0 4.0 3.0

कुछ उदाहरण:

cvtps2pd xmm0, xmm1 ; xmm0 = xmm1 as 2 doubles (low 2 lanes of xmm1 are read)
cvtpd2ps xmm0, xmm1 ; xmm0 = xmm1 as 2 floats (in low 2 lanes; upper 2 zeroed)
मेमोरी ऑपरेंड

ऊपर दिखाए गए सभी SIMD निर्देशों में रजिस्टर-से-रजिस्टर ऑपरेंड थे। ये अपने स्रोत के रूप में मेमोरी ऑपरेंड भी ले सकते हैं (लेकिन अपने गंतव्य के रूप में नहीं)।

मेमोरी से कितने बाइट पढ़े जाएँगे, यह निर्देश तय करता है। उदाहरण के लिए, addps 16 बाइट (4 सिंगल-प्रिसिज़न दशमलव संख्याएँ) पढ़ता है। इसके उलट cvtps2pd सिर्फ 8 बाइट (2 सिंगल-प्रिसिज़न दशमलव संख्याएँ) पढ़ता है, क्योंकि उसे बस इतना ही इनपुट चाहिए।

addps    xmm0, [rsi]      ; xmm0 = xmm0 + 4 floats at [rsi]
mulpd    xmm1, [rsi + 16] ; xmm1 = xmm1 * 2 doubles at [rsi + 16]
cvtps2pd xmm2, [rsi + 8]  ; xmm2 = (2 floats at [rsi + 8]) converted to 2 doubles

जब मेमोरी ऑपरेंड 16 बाइट चौड़ा हो, तो उसे 16-बाइट अलाइन्ड होना चाहिए। अलाइन्ड मेमोरी लोकेशन वह होती है जिसका पता ऑपरेशन के आकार का गुणज हो (यहाँ 16 बाइट)।

अलाइन्ड मेमोरी घोषित करने के लिए आप .data और .rodata सेक्शन पर align डायरेक्टिव इस्तेमाल कर सकते हैं, या .bss सेक्शन पर alignb:

section .rodata
align 16
mem1: dd 4.0, 3.5, 2.0, -1.0

section .data
align 16
mem2: dq 6.0, 2.1

section .bss
alignb 16
mem3: resb 16

section .text
fn:
    movaps xmm0, oword [rel mem1] ; loads 4 packed 32-bit floats in xmm0
    movapd xmm1, oword [rel mem2] ; loads 2 packed 64-bit floats in xmm1
    movaps oword [rel mem3], xmm0 ; stores 4 packed 32-bit floats in mem3
    ret

16 बाइट से छोटे मेमोरी ऑपरेंडों पर अलाइनमेंट की कोई शर्त नहीं होती, जैसे cvtps2pd का 8-बाइट स्रोत।

वैल्यू लोड करना और संग्रहीत करना

चार निर्देश ऐसे हैं जो पैक्ड दशमलव संख्याओं के 128 बिट कॉपी करते हैं। ये दो xmm रजिस्टरों के बीच, या एक xmm रजिस्टर और एक मेमोरी लोकेशन के बीच काम करते हैं:

निर्देश विवरण
movaps पैक्ड सिंगल-प्रिसिज़न डेटा किसी अलाइन्ड मेमोरी लोकेशन से या उस पर कॉपी करता है
movups पैक्ड सिंगल-प्रिसिज़न डेटा किसी अनअलाइन्ड मेमोरी लोकेशन से या उस पर कॉपी करता है
movapd पैक्ड डबल-प्रिसिज़न डेटा किसी अलाइन्ड मेमोरी लोकेशन से या उस पर कॉपी करता है
movupd पैक्ड डबल-प्रिसिज़न डेटा किसी अनअलाइन्ड मेमोरी लोकेशन से या उस पर कॉपी करता है

ये सभी निर्देश दो xmm रजिस्टरों के बीच वैल्यू कॉपी करने के लिए इस्तेमाल किए जा सकते हैं।

अगर ऑपरेशन में कोई मेमोरी लोकेशन शामिल हो, तो movaps और movapd उसका अलाइन्ड होना ज़रूरी मानते हैं। अगर उन्हें अलाइन्ड न होने वाला पता दिया जाए, तो movaps और movapd फॉल्ट पैदा कर देते हैं। ये एक सुरक्षा जाँच का भी काम करते हैं, जो बताती है कि आपके डेटा का लेआउट वैसा ही है जैसा आप सोचते हैं।

दूसरी ओर, movups और movupd किसी भी मेमोरी लोकेशन के साथ इस्तेमाल किए जा सकते हैं, चाहे वह अलाइन्ड हो या न हो।

NASM में 16-बाइट वैल्यू के लिए आकार का उपसर्ग oword होता है।

SIMD एक्सटेंशन

दशकों से x86-64 इकोसिस्टम में SIMD सक्रिय विकास का केंद्र रहा है। आर्किटेक्चर में हर एक्सटेंशन नए निर्देश लाता है, या पुराने निर्देशों के नए इस्तेमाल।

खास तौर पर, AVX और AVX2 एक्सटेंशन कुछ अहम बदलाव लाते हैं।

पहला, 256-बिट SIMD रजिस्टरों का समर्थन, जिन्हें ymm कहते हैं। ये अपने-अपने xmm रजिस्टरों का विस्तार हैं, ठीक वैसे ही जैसे rax में eax समाया होता है।

दूसरा, xmm (और ymm) रजिस्टरों पर काम करने वाले ज़्यादातर निर्देशों के लिए इनमें 3-ऑपरेंड वाले रूपों का समर्थन है। इसमें सिर्फ SIMD निर्देश ही नहीं, बल्कि स्केलर दशमलव संख्याओं पर काम करने वाले निर्देश भी शामिल हैं। ये रूप अपने SSE वाले रूप का ही नोटेशन अपनाते हैं, बस आगे एक v जोड़ दिया जाता है:

vaddsd xmm0, xmm1, xmm2   ; xmm0 = xmm1 + xmm2 (scalar sum of 64-bit floats)
vmulps xmm1, xmm3, xmm4   ; xmm1 = xmm3 * xmm4 (packed multiplication of 32-bit floats)
vmovupd ymm5, yword [mem] ; this copies 32 bytes from the location at mem to ymm5

तीसरा, SIMD मेमोरी ऑपरेंडों के लिए ज़रूरी अलाइनमेंट ढीला कर दिया गया है। इस अलाइनमेंट की ज़रूरत सिर्फ साफ़ तौर पर अलाइन्ड मूव्स को होती है, जैसे vmovaps।

इस ट्रैक में हमने खुद को पिछले SSE एक्सटेंशन तक के निर्देशों और रजिस्टरों तक सीमित रखा है। यह बुनियादी बातें सिखाने के लिए काफी है और बहुत सारे CPU के साथ कंपैटिबिलिटी सुनिश्चित करता है।

लेकिन टेस्ट-रनर AVX और AVX2 का, और यहाँ तक कि AVX-512 का हिस्सा बने नए एक्सटेंशनों का भी, पूरा समर्थन करता है। आप इन्हें बेझिझक इस्तेमाल कर सकते हैं।

Note

SIMD वाले रूपों के इस्तेमाल में एक-सा तरीका अपनाना अच्छी आदत है। कोशिश कीजिए कि एक ही निर्देशों के लिए SSE और AVX वाले रूप आपस में न मिलाएँ।

अगर आपके कोडबेस में ये रूप मिले-जुले हैं, तो अच्छी आदत यह है कि AVX के साथ अपना काम खत्म करने के बाद और SSE इस्तेमाल करने से पहले vzeroupper का इस्तेमाल करें। यह डिपेंडेंसी चेन तोड़ता है और CPU के AVX से SSE स्थिति में बदलते समय होने वाले भारी ठहराव से बचाता है।

निर्देश

आप एक ऑटोमोटिव पार्ट्स फैक्ट्री के फ्लोर पर डेटा सिस्टम के लिए कोड लिख रहे हैं। इंजन और चेसिस के कंपोनेंट मशीनिंग स्टेशनों की एक श्रृंखला से गुज़रते हैं। हर स्टेशन कुछ माप तैयार करता है, और अगले चरण के शुरू होने से पहले उन्हें जोड़ना, तुलना करना, कैलिब्रेट करना या नॉर्मलाइज़ करना पड़ता है। यहाँ आप जो फंक्शन लागू करेंगे, उनमें से हर स्टेशन के लिए एक-एक है।

Note

इस अभ्यास की गणनाएँ स्केलर ऑपरेशन से नहीं, बल्कि SIMD निर्देशों का उपयोग करके की जानी चाहिए।

1. दो लाइनों की यील्ड जोड़िए

फैक्ट्री के फ्लोर पर दो समानांतर असेंबली लाइनें चलती हैं। हर शिफ्ट के अंत में दोनों लाइनें अपनी 4 पार्ट बिनों की यील्ड सिंगल-प्रिसिज़न संख्याओं के रूप में बताती हैं। सुपरवाइज़र के डैशबोर्ड पर हर बिन की जोड़ी हुई यील्ड दिखाई जाती है, इसलिए दोनों रिपोर्टों को बिन-दर-बिन जोड़ा जाता है। तीनों बफर रिपोर्टिंग सिस्टम द्वारा संभाले जाते हैं और हमेशा 16-बाइट अलाइन्ड होते हैं।

एक फंक्शन sum_yields बनाइए। यह line_a और line_b से मिली हर बिन की यील्ड लेकर बिन-दर-बिन जोड़ result में लिखता है। आर्गुमेंट इस क्रम में हैं:

  1. line_a: 16-बाइट अलाइन्ड मेमोरी एड्रेस, जिसमें 4 32-बिट फ्लोटिंग-पॉइंट संख्याएँ रहती हैं
  2. line_b: 16-बाइट अलाइन्ड मेमोरी एड्रेस, जिसमें 4 32-बिट फ्लोटिंग-पॉइंट संख्याएँ रहती हैं
  3. result: 16-बाइट अलाइन्ड मेमोरी एड्रेस, जहाँ फंक्शन 4 32-बिट फ्लोटिंग-पॉइंट संख्याएँ लिखेगा

इस फंक्शन की कोई रिटर्न वैल्यू नहीं होती; इसके बजाय नतीजा पास किए गए बफर (result) में संग्रहीत किया जाता है:

line_a = {10.0, 20.0, 30.0, 40.0};
line_b = { 5.0, 15.0, 25.0, 35.0};
sum_yields(line_a, line_b, result);
// => result == {15.0, 35.0, 55.0, 75.0};

2. स्केल्ड डेविएशन निकालिए

ऑपरेशनों के बीच एक सैंपलिंग स्टेशन मशीन किए गए पार्ट्स को मापता है और उनकी तुलना लक्ष्य आयामों से करता है। मापी गई वैल्यू एक प्रिसिज़न गेज से एक ऐसे बफर में आती हैं, जो हमेशा 16-बाइट अलाइन्ड होता है। लक्ष्य वैल्यू वर्क ऑर्डर पैकेट से आती हैं, जिसकी अलाइनमेंट की कोई गारंटी नहीं होती। डाउनस्ट्रीम टॉलरेंस चेकर को भेजने से पहले डेविएशन को हर अक्ष के लिए अलग सेंसिटिविटी फैक्टर से गुणा किया जाता है। इस चरण में डाउनस्ट्रीम चेकर डेविएशन को मेमोरी से नहीं, बल्कि सीधे एक SIMD रजिस्टर से पढ़ता है।

एक फंक्शन scaled_deviation बनाइए। यह measured, target और sensitivity लेकर स्केल की गई डेविएशन result में लिखता है। आर्गुमेंट इस क्रम में हैं:

  1. measured: 16-बाइट अलाइन्ड मेमोरी एड्रेस, जिसमें 2 64-बिट फ्लोटिंग-पॉइंट संख्याएँ रहती हैं
  2. target: मेमोरी एड्रेस, जिसमें 2 64-बिट फ्लोटिंग-पॉइंट संख्याएँ रहती हैं (अलाइनमेंट की गारंटी नहीं)
  3. sensitivity: मेमोरी एड्रेस, जिसमें 2 64-बिट फ्लोटिंग-पॉइंट संख्याएँ रहती हैं (अलाइनमेंट की गारंटी नहीं)
  4. result: एक बफर का मेमोरी एड्रेस, जहाँ फंक्शन 2 64-बिट फ्लोटिंग-पॉइंट संख्याएँ लिखेगा (अलाइनमेंट की गारंटी नहीं)

स्केल की गई डेविएशन हर लेन में (measured - target) * sensitivity होती है।

इस फंक्शन की कोई रिटर्न वैल्यू नहीं होती; इसके बजाय नतीजा 64-बिट फ्लोटिंग-पॉइंट संख्याओं की एक जोड़ी के रूप में पास किए गए बफर (result) में संग्रहीत किया जाता है।

measured    = {10.5, 20.5};
target      = {10.0, 20.0};
sensitivity = { 2.0,  4.0};
scaled_deviation(measured, target, sensitivity, result);
// => result == {1.0, 2.0};

3. एक बैच को कैलिब्रेट कीजिए

पार्ट्स के फाइनल इंस्पेक्शन बे में जाने से पहले 4 पार्ट्स के बैच की डाइमेंशनल रीडिंग्स को डबल प्रिसिज़न में बदला जाता है और ज्ञात तापमान ड्रिफ्ट के लिए ठीक किया जाता है।

4 कच्ची रीडिंग्स सिंगल-प्रिसिज़न संख्याओं के रूप में आती हैं। 2 रीडिंग्स की हर पंक्ति के लिए कैलिब्रेशन reference / (raw - offset) निकालता है और फिर इस नतीजे को 0.5 के स्थिर कन्वर्ज़न फैक्टर से गुणा करता है।

ध्यान दीजिए कि बैच की दोनों पंक्तियों के लिए reference और offset एक ही हैं। कन्वर्ज़न फैक्टर हर कैलिब्रेशन में एक ही रहता है, इसलिए इसे आर्गुमेंट के रूप में लेने के बजाय आपको इसे मेमोरी में एक पैक्ड 16-बाइट कॉन्स्टेंट के रूप में घोषित करना चाहिए।

एक फंक्शन calibrate_batch बनाइए। यह एक raw बैच, एक reference, एक offset और एक result बफर लेकर कैलिब्रेट की गई वैल्यू result में लिखता है। आर्गुमेंट इस क्रम में हैं:

  1. raw: मेमोरी एड्रेस, जिसमें 4 32-बिट फ्लोटिंग-पॉइंट संख्याएँ रहती हैं (अलाइनमेंट की गारंटी नहीं)
  2. reference: 16-बाइट अलाइन्ड मेमोरी एड्रेस, जिसमें 2 64-बिट फ्लोटिंग-पॉइंट संख्याएँ रहती हैं
  3. offset: 16-बाइट अलाइन्ड मेमोरी एड्रेस, जिसमें 2 64-बिट फ्लोटिंग-पॉइंट संख्याएँ रहती हैं
  4. result: 16-बाइट अलाइन्ड मेमोरी एड्रेस, जहाँ फंक्शन 4 64-बिट फ्लोटिंग-पॉइंट संख्याएँ लिखेगा (कुल 32 बाइट)

फंक्शन को बैच की दोनों पंक्तियों में से हर एक के लिए यह करना चाहिए:

  1. पंक्ति की 2 सिंगल-प्रिसिज़न कच्ची वैल्यू को डबल प्रिसिज़न में बदलिए।
  2. बदली गई जोड़ी में से offset घटाइए।
  3. reference को नतीजे से भाग दीजिए।
  4. हर 64-बिट फ्लोटिंग-पॉइंट संख्या को हर लेन में 0.5 के तय कन्वर्ज़न फैक्टर से गुणा कीजिए।
  5. उन 2 64-बिट फ्लोटिंग-पॉइंट संख्याओं को result के संगत 16 बाइट्स में संग्रहीत कीजिए।

इस फंक्शन की कोई रिटर्न वैल्यू नहीं होती; इसके बजाय नतीजा पास किए गए बफर (result) में संग्रहीत किया जाता है:

raw       = {10.0, 20.0, 25.0, 50.0};
reference = {100.0, 100.0};
offset    = {  5.0,  10.0};
calibrate_batch(raw, reference, offset, result);
// => result == {10.0, 5.0, 2.5, 1.25};

4. लाइन के साथ स्कोर नॉर्मलाइज़ कीजिए

लाइन के अंत में हर पार्ट का एक क्वालिटी स्कोर होता है, जिसे रिपोर्ट करने से पहले नॉर्मलाइज़ करना पड़ता है। हर स्कोर को उसके अपने पार्ट-विशिष्ट गेन से गुणा किया जाता है और फिर हर अक्ष के प्रोडक्शन स्केल से भाग दिया जाता है। गेन्स बफर में हर पार्ट के लिए एक गेन रहता है।

पूरे रन के लिए प्रोडक्शन स्केल एक ही रहता है और इसे एक ऐसे मेमोरी एड्रेस में भेजा जाता है, जिसमें दो 64-बिट फ्लोटिंग-पॉइंट संख्याएँ रहती हैं। स्कोर इन प्लेस अपडेट होते हैं: जिस बफर में पहले कच्चे स्कोर थे, अब उसी में नॉर्मलाइज़ किए गए स्कोर रहते हैं।

एक फंक्शन normalize_scores बनाइए। यह एक scores ऐरे, एक gains ऐरे, एक scale और एक गिनती n लेकर स्कोर को इन प्लेस नॉर्मलाइज़ करता है। आर्गुमेंट इस क्रम में हैं:

  1. scores: 16-बाइट अलाइन्ड मेमोरी एड्रेस, जिसमें n 64-बिट फ्लोटिंग-पॉइंट संख्याएँ रहती हैं (इन प्लेस बदली जाती हैं)
  2. gains: 16-बाइट अलाइन्ड मेमोरी एड्रेस, जिसमें n 64-बिट फ्लोटिंग-पॉइंट संख्याएँ रहती हैं
  3. scale: 16-बाइट अलाइन्ड मेमोरी एड्रेस, जिसमें 2 64-बिट फ्लोटिंग-पॉइंट संख्याएँ रहती हैं
  4. n: एक 64-बिट धनात्मक सम पूर्णांक (कम से कम 2), यानी नॉर्मलाइज़ किए जाने वाले स्कोर की संख्या

दो स्केल लेन हर जोड़ी के दो एलिमेंट पर लागू होती हैं। पहली लेन हर जोड़ी के पहले एलिमेंट को स्केल करती है (सम स्थानों वाली लेन), और दूसरी लेन दूसरे एलिमेंट को (विषम स्थानों वाली लेन)। हर स्कोर को उसके संगत गेन से गुणा किया जाता है और फिर उचित स्केल लेन से भाग दिया जाता है: score * gain / scale_lane।

इस फंक्शन की कोई रिटर्न वैल्यू नहीं होती; इसके बजाय नतीजा इनपुट बफर (scores) में उसी स्थान पर संग्रहीत किया जाता है, जहाँ उस संगत score का था जिसे नॉर्मलाइज़ किया गया:

n      = 4
scores = {10.0, 20.0, 30.0, 40.0};
gains  = { 2.0,  1.5,  1.0,  0.5};
scale  = { 2.0,  4.0};
normalize_scores(scores, gains, scale, n);
// => scores == {10.0, 7.5, 15.0, 5.0};
GitHub के ज़रिए संपादित करें यह लिंक एक नई विंडो या टैब में खुलता है
x86-64 Assembly Exercism

उत्पादन लाइन शुरू करने के लिए तैयार हैं?

Exercism पर साइन अप कीजिए और x86-64 Assembly को 22 कॉन्सेप्ट130 अभ्यास तथा असली इंसानों से मिलने वाली मेंटरिंग के साथ सीखिए और उसमें महारत हासिल कीजिए, वह भी बिल्कुल मुफ्त।