Single instruction, multiple data (SIMD) হলো হার্ডওয়্যার-লেভেল প্যারালালিজমের একটি রূপ (অর্থাৎ একটিমাত্র ইনস্ট্রাকশনের মাধ্যমে একই সময়ে একাধিক ডেটার উপর কাজ করা হয়)। CPU একই সময়ে একাধিক এলিমেন্টের উপর একই গণনা সম্পন্ন করে।
উদাহরণ হিসেবে ধরা যাক, চারটি ৩২-বিট ফ্লোটের দুটি অ্যারে এলিমেন্ট ধরে ধরে যোগ করা হচ্ছে। একটি স্কেলার ইমপ্লিমেন্টেশন প্রত্যেক এলিমেন্টের জন্য আলাদা করে চারটি addss ইনস্ট্রাকশন পাঠাবে। একটি SIMD ইমপ্লিমেন্টেশন একটিমাত্র addps দিয়ে চারটি যোগই সমান্তরালে করে ফেলে। তাতে প্রতি ইনস্ট্রাকশনে চার গুণ কাজ হয়।
x86-64 অ্যাসেম্বলিতে SIMD ইনস্ট্রাকশন সাধারণত xmm রেজিস্টার ব্যবহার করে সম্পাদন করা হয়। এসব ইনস্ট্রাকশনের কিছু ইন্টিজারের উপর কাজ করে। আবার কিছু কাজ করে ফ্লোটিং-পয়েন্ট সংখ্যার উপর।
ওই ইন্টিজার বা ফ্লোটিং-পয়েন্ট সংখ্যাগুলোকেই বলা হয় প্যাকড। এদের প্রত্যেকে একই রেজিস্টারের ভেতরে একটি জায়গা দখল করে, যাকে বলা হয় লেন। SIMD ইনস্ট্রাকশন সাধারণত দুটি সোর্স অপারেন্ডের একই লেন অবস্থানের মানের উপর সমান্তরালে কাজ করে। প্রতিটি অপারেশনের ফলাফল ডেস্টিনেশন অপারেন্ডের একই অবস্থানের লেনে সংরক্ষিত হয়।
একটি লেন কোনো কঠোর সীমা নয়। এটি বরং বাইটের একটি ক্রম সম্পর্কে প্রতিটি ইনস্ট্রাকশনের দেওয়া একটি ব্যাখ্যা। একটি ১২৮-বিট অপারেন্ডে ১৬ বাইট থাকে। ইনস্ট্রাকশন অনুযায়ী সেই বাইটগুলোকে ১৬টি ১-বাইট লেন, ৮টি ২-বাইট লেন, ৪টি ৪-বাইট লেন, ২টি ৮-বাইট লেন, বা এমনকি একটি মাত্র ১৬-বাইট লেন হিসেবে ব্যাখ্যা করা যায়।
উদাহরণস্বরূপ, addps ইনস্ট্রাকশনটি প্যাকড সিঙ্গেল-প্রিসিশন (৪-বাইট) ফ্লোটিং-পয়েন্ট মান যোগ করে। এই ইনস্ট্রাকশনের জন্য অপারেন্ডের প্রতিটি ৪-বাইট খণ্ড আলাদা একটি লেন। ১২৮-বিট চওড়া (১৬-বাইট) একটি অপারেন্ডে:
| অপারেন্ড | ৪র্থ লেন | ৩য় লেন | ২য় লেন | ১ম লেন |
|---|---|---|---|---|
| সোর্স ১ | 1.5 | 2.1 | 6.2 | 3.4 |
| + | + | + | + | + |
| সোর্স ২ | 0.5 | 8.26 | -13.4 | 22.9 |
| = | = | = | = | = |
| ফলাফল | 2.0 | 10.36 | -7.2 | 26.3 |
অন্যদিকে, mulpd-এর ক্ষেত্রে একটি ১২৮-বিট অপারেন্ডে ২টি লেন ধরা হয়। কারণ এই ইনস্ট্রাকশন ডাবল-প্রিসিশন (৮-বাইট) মানের উপর প্যাকড গুণ করে:
| অপারেন্ড | ২য় লেন | ১ম লেন |
|---|---|---|
| সোর্স ১ | 4.2 | 2.1 |
| * | * | * |
| সোর্স ২ | 1.0 | 92.4 |
| = | = | = |
| ফলাফল | 4.2 | 194.04 |
এই কনসেপ্টে আমরা SIMD ইনস্ট্রাকশনের একটি ছোট নির্বাচিত অংশ নিয়ে আলোচনা করব। এগুলো xmm রেজিস্টার ব্যবহার করে প্যাকড ফ্লোটিং-পয়েন্ট মানের উপর কাজ করে।
সাধারণ নিয়ম হিসেবে, ফ্লোটিং-পয়েন্ট মানের উপর কাজ করে এমন SIMD ইনস্ট্রাকশন দুটি অপারেন্ডের রূপ নেয়। এদের নাম একটি নির্দিষ্ট কাঠামো অনুসরণ করে:
add, mul ইত্যাদি)।p, যা বোঝায় ইনস্ট্রাকশনটি প্যাকড ডেটার উপর কাজ করে, ঠিক সেই অবস্থানেই যেখানে আগে ছিল একটি s (স্কেলার ডেটার জন্য)।s এবং ডাবল-প্রিসিশনের জন্য d)।তাই প্যাকড নামটি পাওয়া যায় স্কেলার নামের মনেমোনিকের মাঝখানের s (স্কেলার) কে p (প্যাকড) দিয়ে বদলে দিয়ে। অপারেশনের নাম এবং আকারের সাফিক্স অপরিবর্তিত থাকে:
| স্কেলার | প্যাকড | অর্থ |
|---|---|---|
addss |
addps |
যোগ, সিঙ্গেল-প্রিসিশন (৩২-বিট লেন) |
addsd |
addpd |
যোগ, ডাবল-প্রিসিশন (৬৪-বিট লেন) |
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)-এ ৪টি সিঙ্গেল-প্রিসিশন লেন থাকে। ডেস্টিনেশন (pd)-এ থাকে ২টি ডাবল-প্রিসিশন লেন। সোর্সের শুধু নিচের ২টি লেন পড়া হয়। উপরের ২টি উপেক্ষা করা হয়:
| অপারেন্ড | ৪র্থ লেন | ৩য় লেন | ২য় লেন | ১ম লেন |
|---|---|---|---|---|
ps হিসেবে সোর্স |
উপেক্ষিত | উপেক্ষিত | 2.5 | 1.5 |
pd হিসেবে ডেস্টিনেশন |
2.5 | 1.5 |
cvtpd2ps-এর ক্ষেত্রে সোর্স (pd)-এ ২টি ডাবল-প্রিসিশন লেন থাকে। ডেস্টিনেশন (ps)-এ থাকে ৪টি সিঙ্গেল-প্রিসিশন লেন। সোর্সের দুটি লেনই রূপান্তরিত হয়ে ডেস্টিনেশনের নিচের ২টি লেনে লেখা হয়। ডেস্টিনেশনের উপরের ২টি লেন শূন্য করে দেওয়া হয়:
| অপারেন্ড | ৪র্থ লেন | ৩য় লেন | ২য় লেন | ১ম লেন |
|---|---|---|---|---|
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 পড়ে ১৬ বাইট (৪টি সিঙ্গেল-প্রিসিশন ফ্লোট)। বিপরীতে cvtps2pd পড়ে মাত্র ৮ বাইট (২টি সিঙ্গেল-প্রিসিশন ফ্লোট), কারণ এটুকুই তার প্রয়োজনীয় ইনপুট।
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
যখন মেমরি অপারেন্ডটি ১৬ বাইট চওড়া হয়, তখন এটি ১৬-বাইট অ্যালাইনড হতে হবে। অ্যালাইনড মেমরি লোকেশন বলতে এমন একটি লোকেশন বোঝায়, যার অ্যাড্রেস অপারেশনের আকারের গুণিতক হয় (এক্ষেত্রে ১৬ বাইট)।
অ্যালাইনড মেমরি ঘোষণা করতে, আপনি সেকশন .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
১৬ বাইটের ছোট মেমরি অপারেন্ডের, যেমন cvtps2pd-এর ৮-বাইট সোর্স, কোনো অ্যালাইনমেন্টের প্রয়োজন হয় না।
চারটি ইনস্ট্রাকশন আছে যেগুলো ১২৮ বিট প্যাকড ফ্লোটিং-পয়েন্ট মান কপি করে। এরা দুটি xmm রেজিস্টারের মধ্যে, অথবা একটি xmm রেজিস্টার ও একটি মেমরি লোকেশনের মধ্যে কাজ করে:
| ইনস্ট্রাকশন | বিবরণ |
|---|---|
movaps |
অ্যালাইনড মেমরি লোকেশন থেকে বা সেখানে প্যাকড সিঙ্গেল-প্রিসিশন ডেটা কপি করে |
movups |
আনঅ্যালাইনড মেমরি লোকেশন থেকে বা সেখানে প্যাকড সিঙ্গেল-প্রিসিশন ডেটা কপি করে |
movapd |
অ্যালাইনড মেমরি লোকেশন থেকে বা সেখানে প্যাকড ডাবল-প্রিসিশন ডেটা কপি করে |
movupd |
আনঅ্যালাইনড মেমরি লোকেশন থেকে বা সেখানে প্যাকড ডাবল-প্রিসিশন ডেটা কপি করে |
এই ইনস্ট্রাকশনগুলোর সবগুলোই দুটি xmm রেজিস্টারের মধ্যে মান কপি করতে ব্যবহার করা যায়।
যদি অপারেশনে একটি মেমরি লোকেশন জড়িত থাকে, তবে movaps ও movapd চায় সেটি অ্যালাইনড হোক। অ্যালাইনমেন্টবিহীন অ্যাড্রেস দেওয়া হলে movaps ও movapd একটি ফল্ট তৈরি করবে। এগুলো একইসঙ্গে একটি সেফটি চেকের কাজও করে, যাতে নিশ্চিত হওয়া যায় যে আপনার ডেটার বিন্যাস আপনি যা ভাবছেন ঠিক তাই আছে।
অন্যদিকে, movups ও movupd অ্যালাইনড হোক বা না হোক, যেকোনো মেমরি লোকেশনের সাথে ব্যবহার করা যায়।
NASM-এ ১৬-বাইট মানের সাইজ প্রিফিক্স হলো oword।
দশক ধরে x86-64 ইকোসিস্টেমে SIMD সক্রিয় উন্নয়নের একটি ক্ষেত্র হয়ে আছে। আর্কিটেকচারের প্রতিটি এক্সটেনশন নতুন ইনস্ট্রাকশন বা পুরোনো ইনস্ট্রাকশনের নতুন ব্যবহার নিয়ে আসে।
বিশেষ করে AVX ও AVX2 এক্সটেনশন কয়েকটি গুরুত্বপূর্ণ পরিবর্তন নিয়ে আসে।
প্রথমত, ২৫৬-বিট SIMD রেজিস্টারের সমর্থন, যাদের বলা হয় ymm। এগুলো সংশ্লিষ্ট xmm রেজিস্টারকে বাড়িয়ে দেয়, অনেকটা যেমন rax তার ভেতরে eax ধারণ করে।
দ্বিতীয়ত, xmm (এবং ymm) রেজিস্টারের উপর কাজ করে এমন বেশিরভাগ ইনস্ট্রাকশনের জন্য ৩-অপারেন্ড ভ্যারিয়েন্টের সমর্থন এতে যুক্ত হয়েছে। এর মধ্যে শুধু 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-এর অংশ নতুন নতুন এক্সটেনশনও সম্পূর্ণভাবে সমর্থন করে। আপনি নির্দ্বিধায় সেগুলো ব্যবহার করতে পারেন।
SIMD ভ্যারিয়েন্ট ব্যবহারে সামঞ্জস্যপূর্ণ থাকা ভালো অভ্যাস। একই ইনস্ট্রাকশনের জন্য SSE ও AVX ভ্যারিয়েন্ট মেশানো এড়িয়ে চলুন।
আপনার কোডবেসে যদি এসব ভ্যারিয়েন্ট মেশানো থাকে, তবে AVX নিয়ে কাজ শেষ করার পর এবং SSE ব্যবহার শুরু করার আগে vzeroupper ব্যবহার করা ভালো অভ্যাস।
এটি ডিপেন্ডেন্সি চেইন ভেঙে দেয় এবং CPU যখন AVX ও SSE অবস্থার মধ্যে পরিবর্তিত হয় তখন একটি ব্যয়বহুল স্টল এড়ায়।
আপনি একটি অটোমোটিভ পার্টস কারখানার মেঝেতে থাকা ডেটা সিস্টেমের জন্য কোড লিখছেন। ইঞ্জিন ও চেসিসের যন্ত্রাংশ একের পর এক মেশিনিং স্টেশনের মধ্য দিয়ে যায়। প্রতিটি স্টেশন এমন কিছু পরিমাপ তৈরি করে, যেগুলো পরের ধাপ চালু হওয়ার আগে একত্র করা, তুলনা করা, ক্যালিব্রেট করা বা নরমালাইজ করতে হয়। এখানে আপনি যে ফাংশনগুলো তৈরি করবেন, সেগুলো প্রতিটি স্টেশনের জন্য একটি করে।
এই অনুশীলনীর হিসাবগুলো স্কেলার অপারেশন নয়, বরং SIMD ইনস্ট্রাকশন ব্যবহার করে করতে হবে।
কারখানার মেঝেতে দুটি সমান্তরাল অ্যাসেম্বলি লাইন চলে। প্রতিটি শিফটের শেষে দুটি লাইনই ৪টি পার্ট বিনের জন্য তাদের ইল্ড সিঙ্গেল-প্রিসিশন সংখ্যা হিসেবে রিপোর্ট করে। সুপারভাইজারের ড্যাশবোর্ড প্রতিটি বিনের সম্মিলিত ইল্ড দেখায়, তাই দুটি রিপোর্ট বিন অনুযায়ী যোগ করা হয়। তিনটি বাফারই রিপোর্টিং সিস্টেম দ্বারা পরিচালিত হয় এবং সবসময় ১৬-বাইট অ্যালাইনড থাকে।
এমন একটি ফাংশন sum_yields ডিফাইন করুন, যা line_a ও line_b থেকে পাওয়া প্রতি-বিন ইল্ড নিয়ে বিন অনুযায়ী যোগফল result-এ লেখে।
আর্গুমেন্টগুলো ক্রমানুসারে:
line_a: ৪টি ৩২-বিট ফ্লোটিং-পয়েন্ট সংখ্যা ধারণ করা ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেসline_b: ৪টি ৩২-বিট ফ্লোটিং-পয়েন্ট সংখ্যা ধারণ করা ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেসresult: ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, যেখানে ফাংশনটি ৪টি ৩২-বিট ফ্লোটিং-পয়েন্ট সংখ্যা লিখবেএই ফাংশনের কোনো রিটার্ন ভ্যালু নেই; বরং ফলাফলটি পাস করা বাফারে (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};
অপারেশনগুলোর মাঝে একটি স্যাম্পলিং স্টেশন মেশিন করা যন্ত্রাংশ মাপে এবং সেগুলোকে টার্গেট মাপের সঙ্গে তুলনা করে। মাপা মানগুলো একটি প্রিসিশন গেজ থেকে এমন একটি বাফারে আসতে থাকে, যা সবসময় ১৬-বাইট অ্যালাইনড। টার্গেট মানগুলো আসে ওয়ার্ক অর্ডার প্যাকেট থেকে, যার কোনো অ্যালাইনমেন্ট গ্যারান্টি নেই। ডাউনস্ট্রিম টলারেন্স চেকারের কাছে পাঠানোর আগে বিচ্যুতিকে প্রতি-অক্ষের সেনসিটিভিটি ফ্যাক্টর দিয়ে গুণ করা হয়। এই ধাপে ডাউনস্ট্রিম চেকার মেমরি থেকে নয়, বরং সরাসরি একটি SIMD রেজিস্টার থেকে বিচ্যুতি পড়ে।
এমন একটি ফাংশন scaled_deviation ডিফাইন করুন, যা measured, target ও একটি sensitivity নিয়ে স্কেল করা বিচ্যুতি result-এ লেখে।
আর্গুমেন্টগুলো ক্রমানুসারে:
measured: ২টি ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যা ধারণ করা ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেসtarget: ২টি ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যা ধারণ করা মেমরি অ্যাড্রেস (অ্যালাইনমেন্ট গ্যারান্টিড নয়)sensitivity: ২টি ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যা ধারণ করা মেমরি অ্যাড্রেস (অ্যালাইনমেন্ট গ্যারান্টিড নয়)result: বাফারের জন্য মেমরি অ্যাড্রেস, যেখানে ফাংশনটি ২টি ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যা লিখবে (অ্যালাইনমেন্ট গ্যারান্টিড নয়)স্কেল করা বিচ্যুতি হলো লেন অনুযায়ী (measured - target) * sensitivity.
এই ফাংশনের কোনো রিটার্ন ভ্যালু নেই; বরং ফলাফলটি পাস করা বাফারে (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};
যন্ত্রাংশ চূড়ান্ত ইন্সপেকশন বেতে ঢোকার আগে ৪টি যন্ত্রাংশের একটি ব্যাচের মাত্রা-রিডিং ডাবল প্রিসিশনে রূপান্তর করা হয় এবং জানা তাপমাত্রা-ড্রিফটের জন্য সংশোধন করা হয়।
কাঁচা রিডিং ৪টি আসে সিঙ্গেল-প্রিসিশন সংখ্যা হিসেবে।
২টি রিডিংয়ের প্রতিটি সারির জন্য ক্যালিব্রেশন reference / (raw - offset) হিসাব করে, তারপর এই ফলাফলকে 0.5 ধ্রুবক কনভার্সন ফ্যাক্টর দিয়ে গুণ করে।
লক্ষ্য করুন, ব্যাচের দুটি সারির জন্যই রেফারেন্স ও অফসেট একই। প্রতিটি ক্যালিব্রেশনে কনভার্সন ফ্যাক্টর একই থাকে, তাই এটি আর্গুমেন্ট হিসেবে না নিয়ে মেমরিতে একটি প্যাকড ১৬-বাইট ধ্রুবক হিসেবে ঘোষণা করা উচিত।
এমন একটি ফাংশন calibrate_batch ডিফাইন করুন, যা একটি raw ব্যাচ, একটি reference, একটি offset ও একটি result বাফার নিয়ে ক্যালিব্রেট করা মানগুলো result-এ লেখে।
আর্গুমেন্টগুলো ক্রমানুসারে:
raw: ৪টি ৩২-বিট ফ্লোটিং-পয়েন্ট সংখ্যা ধারণ করা মেমরি অ্যাড্রেস (অ্যালাইনমেন্ট গ্যারান্টিড নয়)reference: ২টি ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যা ধারণ করা ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেসoffset: ২টি ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যা ধারণ করা ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেসresult: ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস, যেখানে ফাংশনটি ৪টি ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যা লিখবে (মোট ৩২ বাইট)ফাংশনটি ব্যাচের দুটি সারির প্রতিটির জন্য এই কাজগুলো করবে:
offset বিয়োগ করবে।reference-কে ফলাফল দিয়ে ভাগ করবে।0.5 স্থির কনভার্সন ফ্যাক্টর দিয়ে লেন অনুযায়ী গুণ করবে।result-এর সংশ্লিষ্ট ১৬ বাইটে সংরক্ষণ করবে।এই ফাংশনের কোনো রিটার্ন ভ্যালু নেই; বরং ফলাফলটি পাস করা বাফারে (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};
লাইনের শেষে প্রতিটি যন্ত্রাংশের একটি কোয়ালিটি স্কোর থাকে, যা রিপোর্ট করার আগে নরমালাইজ করা দরকার। প্রতিটি স্কোরকে তার নিজস্ব প্রতি-যন্ত্রাংশ গেইন দিয়ে গুণ করা হয়, তারপর প্রতি-অক্ষের প্রোডাকশন স্কেল দিয়ে ভাগ করা হয়। গেইন বাফারে প্রতিটি যন্ত্রাংশের জন্য একটি করে গেইন থাকে।
প্রোডাকশন স্কেল পুরো রানের জন্যই একই থাকে এবং ২টি ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যা ধারণ করা একটি মেমরি অ্যাড্রেসে পাস করা হয়। স্কোরগুলো একই জায়গায় আপডেট হয়: যে বাফারে কাঁচা স্কোর ছিল, এখন সেখানেই নরমালাইজ করা স্কোর থাকে।
এমন একটি ফাংশন normalize_scores ডিফাইন করুন, যা একটি scores অ্যারে, একটি gains অ্যারে, একটি scale ও একটি গণনা n নিয়ে স্কোরগুলোকে জায়গাতেই নরমালাইজ করে।
আর্গুমেন্টগুলো ক্রমানুসারে:
scores: n সংখ্যক ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যা ধারণ করা ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেস (জায়গাতেই পরিবর্তিত হয়)gains: n সংখ্যক ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যা ধারণ করা ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেসscale: ২টি ৬৪-বিট ফ্লোটিং-পয়েন্ট সংখ্যা ধারণ করা ১৬-বাইট অ্যালাইনড মেমরি অ্যাড্রেসn: একটি ৬৪-বিট ধনাত্মক জোড় ইন্টিজার (সর্বনিম্ন 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};
Exercism-এ সাইন আপ করুন, x86-64 Assembly ট্র্যাকের 22টি কনসেপ্ট130টি অনুশীলনী আর সত্যিকারের মানুষের মেন্টরিং দিয়ে শিখুন ও দক্ষ হয়ে উঠুন, সম্পূর্ণ বিনামূল্যে।