डेटा-ओरिएंटेड माइंडसेट
डेटा-उन्मुख डिज़ाइन का मतलब यह नहीं है कि हर जगह SoAs लागू करें। इसका सीधा मतलब है कि डेटा प्रतिनिधित्व पर एक प्रमुख फोकस के साथ आर्किटेक्चर को डिजाइन करना - विशेष रूप से कुशल मेमोरी लेआउट और मेमोरी एक्सेस पर ध्यान देने के साथ।
संभवत: जब उपयुक्त हो तो SoA प्रतिनिधि को जन्म दे सकता है:
struct BallSoa
{
vector<float> x; // size n
vector<float> y; // size n
vector<float> z; // size n
vector<float> r; // size n
};
... यह अक्सर ऊर्ध्वाधर लूपि लॉजिक के लिए उपयुक्त है जो एक क्षेत्र केंद्र वेक्टर घटकों और त्रिज्या को एक साथ संसाधित नहीं करता है (चार फ़ील्ड एक साथ गर्म नहीं होते हैं), लेकिन एक समय में एक (त्रिज्या के माध्यम से एक लूप, एक और 3 लूप) गोले केंद्रों के व्यक्तिगत घटकों के माध्यम से)।
अन्य मामलों में एओएस का उपयोग करना अधिक उपयुक्त हो सकता है यदि खेतों को अक्सर एक साथ एक्सेस किया जाता है (यदि आपका लूपि लॉजिक व्यक्तिगत रूप से नहीं बल्कि गेंदों के सभी क्षेत्रों से गुजर रहा है) और / या यदि एक गेंद की यादृच्छिक-पहुंच की आवश्यकता है:
struct BallAoS
{
float x;
float y;
float z;
float r;
};
vector<BallAoS> balls; // size n
... अन्य मामलों में यह एक हाइब्रिड का उपयोग करने के लिए उपयुक्त हो सकता है जो दोनों लाभों को संतुलित करता है:
struct BallAoSoA
{
float x[8];
float y[8];
float z[8];
float r[8];
};
vector<BallAoSoA> balls; // size n/8
... आप आधी गेंद को कैश लाइन / पृष्ठ में अधिक बॉल फ़ील्ड में फिट करने के लिए आधी-फ्लोट का उपयोग करके गेंद के आकार को आधा कर सकते हैं।
struct BallAoSoA16
{
Float16 x2[16];
Float16 y2[16];
Float16 z2[16];
Float16 r2[16];
};
vector<BallAoSoA16> balls; // size n/16
... शायद यहां तक कि त्रिज्या केंद्र क्षेत्र के रूप में लगभग अक्सर एक्सेस नहीं किया जाता है (शायद आपका कोडबेस अक्सर उन्हें बिंदुओं की तरह व्यवहार करता है और केवल शायद ही कभी क्षेत्र के रूप में, उदाहरण के लिए)। उस स्थिति में, आप आगे एक गर्म / ठंडे क्षेत्र की बंटवारे की तकनीक लागू कर सकते हैं।
struct BallAoSoA16Hot
{
Float16 x2[16];
Float16 y2[16];
Float16 z2[16];
};
vector<BallAoSoA16Hot> balls; // size n/16: hot fields
vector<Float16> ball_radiuses; // size n: cold fields
डेटा-ओरिएंटेड डिज़ाइन की कुंजी यह है कि इन सभी प्रकार के अभ्यावेदन पर आपके डिज़ाइन निर्णय लेने में जल्दी से विचार करें, अपने आप को इसके पीछे एक सार्वजनिक इंटरफ़ेस के साथ उप-इष्टतम प्रतिनिधित्व में नहीं फंसाना है।
यह मेमोरी एक्सेस पैटर्न और लेआउट के साथ स्पॉटलाइट डालता है, जिससे उन्हें सामान्य से काफी मजबूत चिंता का विषय बना दिया जाता है। एक अर्थ में यह कुछ हद तक अमूर्तता को भी फाड़ सकता है। मैंने इस मानसिकता को और अधिक लागू करते हुए पाया है कि मैं अब इसे नहीं देखता हूं std::deque
, उदाहरण के लिए, इसकी एल्गोरिदमिक आवश्यकताओं के संदर्भ में, जितना कि इसके समीपवर्ती खंडों का प्रतिनिधित्व है, और यह कैसे यादृच्छिक-पहुंच स्मृति स्तर पर काम करता है। यह कुछ हद तक कार्यान्वयन विवरणों पर ध्यान केंद्रित कर रहा है, लेकिन कार्यान्वयन विवरण जो प्रदर्शन पर प्रभाव या प्रभाव के रूप में एल्गोरिदमिक जटिलता के रूप में स्केलेबिलिटी का वर्णन करते हैं।
समय से पहले अनुकूलन
डेटा-ओरिएंटेड डिज़ाइन का बहुत सारा ध्यान केंद्रित होगा, कम से कम एक नज़र में, समय से पहले अनुकूलन के करीब खतरनाक रूप से। अनुभव अक्सर हमें सिखाता है कि इस तरह के माइक्रो-ऑप्टिमाइज़ेशन सबसे अच्छे तरीके से हंडाइट में लागू होते हैं, और हाथ में एक प्रोफाइलर के साथ।
फिर भी शायद डेटा-उन्मुख डिज़ाइन से लेने के लिए एक मजबूत संदेश ऐसी आशाओं के लिए जगह छोड़ना है। डेटा-उन्मुख मानसिकता को अनुमति देने में मदद मिल सकती है:
अधिक प्रभावी अभ्यावेदन का पता लगाने के लिए डेटा-उन्मुख डिज़ाइन श्वास कक्ष छोड़ सकता है। यह जरूरी नहीं कि मेमोरी लेआउट पूर्णता को एक बार में प्राप्त करने के बारे में है, लेकिन तेजी से-इष्टतम प्रतिनिधित्व की अनुमति देने के लिए अग्रिम में उचित विचार करने के बारे में अधिक है।
दानेदार वस्तु-उन्मुख डिजाइन
बहुत से डेटा-उन्मुख डिज़ाइन चर्चाएँ ऑब्जेक्ट-ओरिएंटेड प्रोग्रामिंग की शास्त्रीय धारणाओं के खिलाफ खुद को गड्ढे में डाल देंगी। फिर भी मैं इसे देखने का एक तरीका पेश करूंगा, जो ओआरओपी को पूरी तरह से खारिज करने के रूप में कट्टर नहीं है।
ऑब्जेक्ट-ओरिएंटेड डिज़ाइन के साथ कठिनाई यह है कि यह अक्सर हमें बहुत दानेदार स्तर पर मॉडल इंटरफेस के लिए लुभाता है, जिससे हमें एक समानांतर थोक मानसिकता के बजाय एक स्केलर, एक-एक-समय मानसिकता के साथ फंस जाता है।
एक अतिरंजित उदाहरण के रूप में, एक छवि के एकल पिक्सेल पर लागू ऑब्जेक्ट-उन्मुख डिज़ाइन मानसिकता की कल्पना करें।
class Pixel
{
public:
// Pixel operations to blend, multiply, add, blur, etc.
private:
Image* image; // back pointer to access adjacent pixels
unsigned char rgba[4];
};
उम्मीद है कि कोई भी वास्तव में ऐसा नहीं करता है। उदाहरण को वास्तव में सकल बनाने के लिए, मैंने पिक्सेल से युक्त बैक पॉइंटर को संग्रहीत किया, ताकि वह धुंधला जैसे छवि प्रसंस्करण एल्गोरिदम के लिए पड़ोसी पिक्सेल तक पहुंच सके।
इमेज बैक पॉइंटर तुरंत एक चमकता हुआ ओवरहेड जोड़ता है, लेकिन भले ही हमने इसे बाहर कर दिया (केवल पिक्सेल का सार्वजनिक इंटरफ़ेस संचालन प्रदान करता है जो एकल पिक्सेल पर लागू होता है), हम एक पिक्सेल का प्रतिनिधित्व करने के लिए सिर्फ एक वर्ग के साथ समाप्त होते हैं।
अब एक सी ++ संदर्भ में इस बैक पॉइंटर के अलावा तत्काल ओवरहेड अर्थ में एक वर्ग के साथ कुछ भी गलत नहीं है। C ++ कंपाइलर का ऑप्टिमाइज़ करना हमारे द्वारा बनाई गई सभी संरचना को लेने और इसे स्मिथेरेंस तक सीमित करने में बहुत अच्छा है।
यहाँ कठिनाई यह है कि हम एक पिक्सेल स्तर के दानेदार पर एक संक्षिप्त इंटरफ़ेस मॉडलिंग कर रहे हैं। यह हमें इस प्रकार के दानेदार डिजाइन और डेटा के साथ फंसा देता है, संभावित रूप से बड़ी संख्या में ग्राहक निर्भरताएं उन्हें इस Pixel
इंटरफ़ेस के लिए युग्मित करती हैं ।
समाधान: एक दानेदार पिक्सेल की ऑब्जेक्ट-ओरिएंटेड संरचना को दूर करें, और एक बड़ी संख्या में पिक्सेल (छवि स्तर पर) के साथ काम करते हुए एक मोटे स्तर पर अपने इंटरफेस की शुरुआत करें।
थोक छवि स्तर पर मॉडलिंग करके, हमारे पास अनुकूलन करने के लिए काफी अधिक जगह है। उदाहरण के लिए, हम 16x16 पिक्सेल की तराशी हुई टाइलों के रूप में बड़ी छवियों का प्रतिनिधित्व कर सकते हैं जो 64-बाइट कैश लाइन में पूरी तरह से फिट होती हैं, लेकिन आम तौर पर छोटी स्ट्राइड के साथ पिक्सेल के कुशल पड़ोसी ऊर्ध्वाधर पहुंच की अनुमति देते हैं (यदि हमारे पास कई छवि प्रसंस्करण एल्गोरिदम हैं जो कि) एक कट्टरपंथी डेटा उन्मुख उदाहरण के रूप में एक ऊर्ध्वाधर फैशन में पड़ोसी पिक्सल का उपयोग करने की आवश्यकता है।
एक मोटे स्तर पर डिजाइनिंग
एक छवि स्तर पर मॉडलिंग इंटरफेस का उपरोक्त उदाहरण एक नो-ब्रेनर उदाहरण की तरह है क्योंकि छवि प्रसंस्करण एक बहुत ही परिपक्व क्षेत्र है जिसका अध्ययन और मृत्यु के लिए अनुकूलित किया गया है। फिर भी कम स्पष्ट एक कण एमिटर में एक कण हो सकता है, स्प्राइट बनाम स्प्राइट का एक संग्रह, किनारों के ग्राफ में एक किनारे, या यहां तक कि एक व्यक्ति बनाम लोगों का एक संग्रह।
डेटा-उन्मुख ऑप्टिमाइज़ेशन (दूरदर्शिता या बाधा में) की अनुमति देने की कुंजी अक्सर थोक में बहुत मोटे स्तर पर इंटरफेस डिजाइन करने के लिए उबालने वाली होती है। एकल इकाइयों के लिए इंटरफेस के डिजाइन का विचार उन बड़े संचालनों वाली संस्थाओं के संग्रह के लिए डिजाइन करके प्रतिस्थापित किया जाता है जो उन्हें थोक में संसाधित करते हैं। यह विशेष रूप से और तुरंत अनुक्रमिक एक्सेस लूप को लक्षित करता है जो सब कुछ एक्सेस करने की आवश्यकता है और मदद नहीं कर सकता है लेकिन रैखिक जटिलता है।
डेटा-उन्मुख डिज़ाइन अक्सर थोक में मॉडलिंग डेटा एकत्र करने के लिए डेटा को मजबूत करने के विचार से शुरू होता है। एक समान मानसिकता इंटरफ़ेस डिज़ाइन के साथ गूँजती है जो इसके साथ होती है।
यह सबसे मूल्यवान सबक है जो मैंने डेटा-ओरिएंटेड डिज़ाइन से लिया है, क्योंकि मैं कंप्यूटर आर्किटेक्चर-सेवी नहीं हूं, जो अक्सर मेरी पहली कोशिश में किसी चीज़ के लिए सबसे इष्टतम मेमोरी लेआउट खोजने के लिए पर्याप्त है। यह कुछ ऐसा हो जाता है जिसे मैं हाथ में एक प्रोफाइलर के साथ (और कभी-कभी कुछ मिस के साथ जहां मैं चीजों को गति देने में विफल रहा था) के साथ पुनरावृति करता हूं। फिर भी डेटा-उन्मुख डिज़ाइन का इंटरफ़ेस डिज़ाइन पहलू वह है जो मुझे अधिक से अधिक कुशल डेटा अभ्यावेदन प्राप्त करने के लिए कमरे में छोड़ देता है।
कुंजी एक मोटे स्तर पर इंटरफेस डिजाइन करने की तुलना में हम आमतौर पर ऐसा करने के लिए परीक्षा देते हैं। यह भी अक्सर आभासी कार्यों, समारोह सूचक कॉल, dylib कॉल और इनबिल्ड होने के लिए असमर्थता के साथ जुड़े गतिशील प्रेषण उपरि को कम करने की तरह साइड लाभ है। इस सब से बाहर निकालने का मुख्य विचार एक थोक फैशन (जब लागू हो) में प्रसंस्करण को देखना है।
ball->do_something();
बनामball_table.do_something(ball)
) कुरूप हो जाता है(&ball_table, index)
।