पोर्टेबल मल्टीकोर / NUMA मेमोरी एलोकेशन / इनिशियलाइज़ेशन बेस्ट प्रैक्टिस


17

जब मेमोरी बैंडविड्थ सीमित संगणनाएं साझा मेमोरी वातावरण में की जाती हैं (जैसे कि ओपनएमपी, थ्रेड्स या टीबीबी के माध्यम से पिरोया गया), तो इस बात की दुविधा है कि यह सुनिश्चित करने के लिए कि मेमोरी को भौतिक मेमोरी में सही तरीके से वितरित किया गया है , जैसे कि प्रत्येक थ्रेड पर स्मृति तक पहुंच होती है। "स्थानीय" मेमोरी बस। हालांकि इंटरफेस पोर्टेबल नहीं हैं, अधिकांश ऑपरेटिंग सिस्टम में थ्रेड एफिनिटी (उदाहरण के लिए pthread_setaffinity_np(), कई POSIX सिस्टम, sched_setaffinity()लिनक्स SetThreadAffinityMask()पर , विंडोज पर) सेट करने के तरीके हैं । मेमोरी पदानुक्रम निर्धारित करने के लिए hwloc जैसी लाइब्रेरी भी हैं , लेकिन दुर्भाग्य से, अधिकांश ऑपरेटिंग सिस्टम अभी तक NUMA मेमोरी पॉलिसी सेट करने के तरीके प्रदान नहीं करते हैं। लिनक्स एक उल्लेखनीय अपवाद है, जिसमें लिबनुमा हैएप्लिकेशन को पेज ग्रैन्युलैरिटी में मेमोरी पॉलिसी और पेज माइग्रेशन में हेरफेर करने की अनुमति देता है (2004 से मेनलाइन में, इस प्रकार व्यापक रूप से उपलब्ध है)। अन्य ऑपरेटिंग सिस्टम उपयोगकर्ताओं से एक अंतर्निहित "पहले स्पर्श" नीति का पालन करने की उम्मीद करते हैं।

"पहले स्पर्श" नीति के साथ काम करने का मतलब है कि कॉल करने वाले को अपनी आत्मीयता के साथ धागे को बनाना और वितरित करना चाहिए, जिसकी वे पहले उपयोग की गई योजना के बाद ताज़ी आवंटित की गई मेमोरी में लिखते हैं। (बहुत कम सिस्टम ऐसे कॉन्फ़िगर किए गए हैं जो malloc()वास्तव में पृष्ठों को ढूंढते हैं, यह सिर्फ उन्हें खोजने का वादा करता है जब वे वास्तव में दोषपूर्ण होते हैं, शायद अलग-अलग थ्रेड्स द्वारा।) इसका मतलब यह है कि आवंटन का उपयोग calloc()करने के बाद मेमोरी का उपयोग करना या तुरंत आबंटन करना memset()हानिकारक है क्योंकि यह गलती की ओर बढ़ेगा। कोर थ्रेड की मेमोरी बस में सभी मेमोरी, जो कि थ्रेडिंग आवंटित की जाती है, सबसे खराब मेमोरी बैंडविड्थ के लिए अग्रणी होती है जब मेमोरी को कई थ्रेड्स से एक्सेस किया जाता है। यही बात C ++ newऑपरेटर पर लागू होती है, जो कई नए आवंटन को शुरू करने पर जोर देता है (जैसेstd::complex)। इस पर्यावरण के बारे में कुछ टिप्पणियां:

  • आवंटन को "थ्रेड कलेक्टिव" बनाया जा सकता है, लेकिन अब आवंटन थ्रेडिंग मॉडल में मिश्रित हो जाता है, जो पुस्तकालयों के लिए अवांछनीय है, जो अलग-अलग थ्रेडिंग मॉडल (शायद अपने स्वयं के थ्रेड पूल के साथ) का उपयोग करके ग्राहकों के साथ बातचीत कर सकते हैं।
  • RAII को मुहावरेदार C ++ का एक महत्वपूर्ण हिस्सा माना जाता है, लेकिन यह NUM वातावरण में स्मृति प्रदर्शन के लिए सक्रिय रूप से हानिकारक लगता है। प्लेसमेंट newका उपयोग मेमोरी से malloc()या रूटीन के माध्यम से आवंटित के साथ किया जा सकता है libnuma, लेकिन यह आवंटन प्रक्रिया को बदलता है (जो मुझे विश्वास है कि आवश्यक है)।
  • EDIT: ऑपरेटर के बारे में मेरा पहले का बयान newगलत था, यह कई तर्कों का समर्थन कर सकता है, चेतन का जवाब देखें। मेरा मानना ​​है कि निर्दिष्ट आत्मीयता का उपयोग करने के लिए पुस्तकालय या एसटीएल कंटेनर प्राप्त करने की चिंता अभी भी है। कई क्षेत्रों को पैक किया जा सकता है और यह सुनिश्चित करने के लिए असुविधाजनक हो सकता है, जैसे, std::vectorसही संदर्भ प्रबंधक सक्रिय के साथ एक reallocates।
  • प्रत्येक थ्रेड अपनी निजी मेमोरी को आवंटित और दोष कर सकता है, लेकिन फिर पड़ोसी क्षेत्रों में अनुक्रमण अधिक जटिल है। (एक विरल मैट्रिक्स वेक्टर उत्पाद पर विचार करें मैट्रिक्स और वैक्टर की एक पंक्ति विभाजन के साथ; के बिना स्वामित्व वाले भाग का अनुक्रमण एक्स एक और अधिक जटिल डेटा संरचना जब आवश्यकता है एक्स । आभासी स्मृति में सन्निहित नहीं है)yएक्सएक्सएक्स

क्या NUMA आवंटन / आरंभीकरण का कोई समाधान मुहावरेदार माना जाता है? क्या मैंने अन्य महत्वपूर्ण गोचरों को छोड़ दिया है?

(मैं अपने सी ++ उदाहरणों के लिए उस भाषा पर जोर देने के लिए उदाहरण नहीं देता हूं, हालांकि सी ++ भाषा स्मृति प्रबंधन के बारे में कुछ फैसले बताती है कि सी जैसी भाषा नहीं होती है, इस प्रकार सी ++ प्रोग्रामर का सुझाव देते समय अधिक प्रतिरोध करने की प्रवृत्ति होती है। चीजें अलग ढंग से।)

जवाबों:


7

इस समस्या का एक समाधान जो मुझे पसंद है वह है, प्रभावी ढंग से, मेमोरी कंट्रोलर स्तर पर थ्रेड्स और (MPI) कार्यों को अलग करना। यानी, प्रत्येक कार्य के तहत CPU सॉकेट या मेमोरी कंट्रोलर और फिर थ्रेड्स के द्वारा NUMA पहलुओं को अपने कोड से हटा दें। यदि आप इसे इस तरह से करते हैं, तो आपको उस सॉकेट / नियंत्रक को सभी मेमोरी को पहले-स्पर्श या उपलब्ध एपीआई में से एक के माध्यम से सुरक्षित रूप से बाँधने में सक्षम होना चाहिए, कोई फर्क नहीं पड़ता कि कौन सा धागा वास्तव में आवंटन या आरंभीकरण का काम करता है। संदेश को सॉकेट्स के बीच गुजरना आमतौर पर बहुत अच्छी तरह से अनुकूलित किया जाता है, एमपीआई में बहुत कम से कम। आपके पास हमेशा इससे अधिक MPI कार्य हो सकते हैं, लेकिन आपके द्वारा उठाए गए मुद्दों के कारण, मैं शायद ही कभी लोगों को कम सलाह देता हूं।


1
यह एक व्यावहारिक समाधान है, लेकिन भले ही हम तेजी से अधिक कोर प्राप्त कर रहे हैं, लेकिन एनयूएमए नोड के प्रति कोर की संख्या लगभग 4 पर काफी स्थिर है। इसलिए काल्पनिक 1000 कोर नोड पर, क्या हम 250 एमपीआई प्रक्रियाएं चला रहे हैं? (यह बहुत अच्छा होगा, लेकिन मुझे संदेह है।)
जेड ब्राउन

मैं असहमत हूं कि प्रति NUMA कोर की संख्या स्थिर है। सैंडी ब्रिज E5 में 8. मैगी कोर्ट्स थे। मुझे 10.m इंटरलेगोस (ORNL टाइटन) के साथ एक Westmere-EX नोड मिला है। 20. नाइट्स कॉर्नर में 50 से अधिक होंगे। मुझे लगता है कि प्रति NUMA कोर रख रहे हैं। मूर के कानून के साथ, कम या ज्यादा।
बिल बर्थ

मैग्नी कोर्ट और इंटरलागोस के विभिन्न NUMA क्षेत्रों में दो मौतें होती हैं, इस प्रकार 6 और 8 NUMA क्षेत्र में कोर। 2006 के लिए फिर से शुरू करें, जहां क्वाड-कोर क्लोवरटाउन के दो सॉकेट मेमोरी के लिए एक ही इंटरफ़ेस (ब्लैकफ़ोर्ड चिपसेट) साझा कर रहे हैं और यह मुझे नहीं दिखता है कि प्रति NUMA क्षेत्र में कोर की संख्या इतनी तेज़ी से बढ़ रही है। ब्लू जीन / क्यू स्मृति के इस सपाट दृश्य को थोड़ा आगे बढ़ाता है और हो सकता है कि नाइट कॉर्नर एक और कदम उठाएगा (हालांकि यह एक अलग उपकरण है, इसलिए शायद हमें GPUs की तुलना में होना चाहिए, जहां हमारे पास 15 (Fermi) या अब 8 हैं ( केप्लर) फ्लैट मेमोरी देखने वाले एसएम)।
जेड ब्राउन

एएमडी चिप्स पर अच्छा कॉल। मैं भूल गया था। फिर भी, मुझे लगता है कि आप थोड़ी देर के लिए इस क्षेत्र में निरंतर वृद्धि देखने जा रहे हैं।
बिल बैर्थ

6

यह उत्तर प्रश्न में दो सी ++ संबंधित गलत धारणाओं के जवाब में है।

  1. "यह C ++ नए ऑपरेटर पर लागू होता है जो नए आवंटन (POD सहित) को शुरू करने पर जोर देता है"
  2. "C ++ ऑपरेटर नया केवल एक पैरामीटर लेता है"

यह आपके द्वारा उल्लिखित मल्टी-कोर मुद्दों के लिए एक सीधा जवाब नहीं है। बस उन टिप्पणियों का जवाब देना जो C ++ प्रोग्रामर को C ++ ज़ीलोट्स के रूप में वर्गीकृत करते हैं ताकि प्रतिष्ठा बनी रहे;)।

1. सी + + "नया" या स्टैक आवंटन को इंगित करने के लिए नई वस्तुओं को शुरू करने पर जोर नहीं दिया जाता है, चाहे पीओडी या नहीं। उपयोगकर्ता द्वारा परिभाषित कक्षा का डिफ़ॉल्ट कंस्ट्रक्टर की जिम्मेदारी है। नीचे पहला कोड जंक प्रिंट से पता चलता है कि कक्षा POD है या नहीं।

2 बिंदु पर। C ++ कई तर्कों के साथ "नया" ओवरलोडिंग की अनुमति देता है। नीचे दूसरा कोड एकल वस्तुओं को आवंटित करने के लिए इस तरह के एक मामले को दर्शाता है। यह एक विचार देना चाहिए और शायद आपके पास उस स्थिति के लिए उपयोगी हो। ऑपरेटर नया [] उचित रूप से भी संशोधित किया जा सकता है।

// पॉइंट 1 के लिए कोड।

#include <iostream>

struct A
{
    // int/double/char/etc not inited with 0
    // with or without this constructor
    // If present, the class is not POD, else it is.
    A() { }

    int i;
    double d;
    char c[20];
};

int main()
{
    A* a = new A;
    std::cout << a->i << ' ' << a->d << '\n';
    for(int i = 0; i < 20; ++i)
        std::cout << (int) a->c[i] << '\n';
}

इंटेल का 11.1 संकलक इस आउटपुट को दिखाता है (जो कि निश्चित रूप से "" a "द्वारा इंगित की गई असिंचित मेमोरी है)।

993001483 6.50751e+029
105
108
... // skipped
97
108

// पॉइंट 2 के लिए कोड।

#include <cstddef>
#include <iostream>
#include <new>

// Just to use two different classes.
class arena { };
class policy { };

struct A
{
    void* operator new(std::size_t, arena& arena_obj, policy& policy_obj)
    {
        std::cout << "special operator new\n";
        return (void*)0x1234; //Just to test
    }
};

void* operator new(std::size_t, arena& arena_obj, policy& policy_obj)
{
    std::cout << "special operator new (global)\n";
    return (void*)0x5678; //Just to test
}

int main ()
{
    arena arena_obj;
    policy policy_obj;
    A* ptr = new(arena_obj, policy_obj) A;
    int* iptr = new(arena_obj, policy_obj) int;
    std::cout << ptr << "\n";
    std::cout << iptr << "\n";
}

सुधार के लिए धन्यवाद। यह सी ++ गैर पॉड सरणियों जैसे के अलावा सी के सापेक्ष नहीं वर्तमान अतिरिक्त जटिलताओं, करता है कि लगता है std::complexजो कर रहे हैं स्पष्ट रूप से प्रारंभ।
जेड ब्राउन

1
@JedBrown: कारण संख्या 6 का उपयोग करने से बचने के लिए std::complex?
जैक पोल्सन

1

Deal.II में हमने थ्रेडिंग बिल्डिंग ब्लॉक्स का उपयोग करके प्रत्येक कोर पर प्रत्येक सेल पर असेंबली को समानांतर करने के लिए सॉफ्टवेयर इन्फ्रास्ट्रक्चर प्राप्त किया है (संक्षेप में, आपके पास प्रति सेल एक कार्य है और उपलब्ध प्रोसेसर पर इन कार्यों को शेड्यूल करने की आवश्यकता है - यही नहीं कार्यान्वित किया गया लेकिन यह सामान्य विचार है)। समस्या यह है कि स्थानीय एकीकरण के लिए आपको कई अस्थायी (खरोंच) वस्तुओं की आवश्यकता होती है और आपको कम से कम उतने प्रदान करने की आवश्यकता होती है, जितने कार्य हैं जो समानांतर में चल सकते हैं। हम खराब गति देखते हैं, संभवत: क्योंकि जब किसी कार्य को प्रोसेसर पर रखा जाता है तो यह एक स्क्रैच ऑब्जेक्ट को पकड़ लेता है जो आमतौर पर किसी अन्य कोर के कैश में होगा। हमारे दो सवाल थे:

(i) क्या यह वास्तव में कारण है? जब हम कैशेग्रिंड के तहत प्रोग्राम चलाते हैं तो मैं देखता हूं कि मैं मूल रूप से एक ही थ्रेड पर प्रोग्राम को चलाने के दौरान निर्देशों की एक ही संख्या का उपयोग कर रहा हूं, फिर भी सभी थ्रेड्स पर जमा हुआ कुल रन-टाइम सिंगल-थ्रेड की तुलना में बहुत बड़ा है। क्या यह वास्तव में है क्योंकि मैं लगातार कैश को दोष देता हूं?

(ii) मैं यह कैसे पता लगा सकता हूं कि मैं कहां हूं, जहां प्रत्येक खरोंच वस्तुएं हैं, और मेरे वर्तमान कोर के कैश में जो गर्म है उसे एक्सेस करने के लिए मुझे कौन सी स्क्रैच ऑब्जेक्ट लेनी होगी?

अंततः, हमें इनमें से किसी भी समाधान के उत्तर नहीं मिले हैं और कुछ कार्यों के बाद फैसला किया है कि हमारे पास इन समस्याओं की जांच करने और हल करने के लिए उपकरणों की कमी है। मुझे पता है कि कम से कम सिद्धांत रूप में समस्या को कैसे हल किया जाए (ii) (अर्थात, थ्रेड-लोकल ऑब्जेक्ट्स का उपयोग करके, मान लें कि थ्रेड्स प्रोसेसर कोर पर पिन किए जाते हैं - एक और अनुमान जो परीक्षण के लिए मामूली नहीं है), लेकिन मेरे पास समस्या का परीक्षण करने के लिए कोई उपकरण नहीं है (मैं)।

इसलिए, हमारे दृष्टिकोण से, NUMA से निपटना अभी भी एक अनसुलझा प्रश्न है।


आपको अपने थ्रेड को सॉकेट से बांधना चाहिए ताकि आपको आश्चर्य न हो कि प्रोसेसर पिन किए गए हैं या नहीं। लिनक्स सामान को चारों ओर ले जाना पसंद करता है।
बिल बर्थ

इसके अलावा, नमूना getcpu () या schedule_getcpu () (आपके libc और कर्नेल पर निर्भर करता है और क्या नहीं) आपको यह निर्धारित करने की अनुमति देना चाहिए कि थ्रेड लिनक्स पर कहां चल रहे हैं।
बिल बर्थ

हां, और मुझे लगता है कि थ्रेडिंग बिल्डिंग ब्लॉक जो हम प्रोसेसर पर थ्रेड पिन थ्रेड्स पर काम करने के लिए उपयोग करते हैं। यही कारण है कि हमने थ्रेड-लोकल स्टोरेज के साथ काम करने की कोशिश की। लेकिन मेरी समस्या (i) के समाधान के साथ आना मेरे लिए अभी भी मुश्किल है।
वोल्फगैंग बैंगर्थ

1

Hwloc के अलावा कुछ उपकरण हैं जो HPC क्लस्टर के मेमोरी वातावरण पर रिपोर्ट कर सकते हैं और जिसका उपयोग विभिन्न प्रकार के NUMA कॉन्फ़िगरेशन सेट करने के लिए किया जा सकता है।

मैं एक ऐसे उपकरण के रूप में LIKWID की सिफारिश करूंगा क्योंकि यह एक कोड आधारित दृष्टिकोण से बचता है जो आपको एक प्रक्रिया को कोर में पिन करने की अनुमति देता है। मशीन विशिष्ट मेमोरी कॉन्फ़िगरेशन को संबोधित करने के लिए टूलिंग का यह तरीका क्लस्टर के पार आपके कोड की पोर्टेबिलिटी सुनिश्चित करने में मदद करेगा।

आप इसे ISC'13 " LIKWID - लाइटवेट परफॉरमेंस टूल्स " से रेखांकित करते हुए एक छोटी प्रस्तुति पा सकते हैं और लेखकों ने Arxiv पर एक पेपर प्रकाशित किया है "HPM- असिस्टेड परफॉर्मेंस इंजीनियरिंग फॉर मॉडर्न मल्टीकोर प्रोसेसर "। यह पेपर आपकी मशीन की वास्तुकला और मेमोरी टोपोलॉजी के लिए विशिष्ट कोड को विकसित करने के लिए हार्डवेयर काउंटर से डेटा की व्याख्या करने के लिए एक दृष्टिकोण का वर्णन करता है।


LIKWID उपयोगी है, लेकिन सवाल यह था कि संख्यात्मक / मेमोरी-संवेदी पुस्तकालयों को कैसे लिखा जाए, जो निष्पादन वातावरण, थ्रेडिंग योजनाओं, MPI संसाधन प्रबंधन और आत्मीयता-सेटिंग, उपयोग के साथ विभिन्न प्रकार के क्षेत्रों में विश्वसनीय रूप से प्राप्त कर सकते हैं और स्वयं-ऑडिट कर सकते हैं। अन्य पुस्तकालय, आदि
जेड ब्राउन
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.