स्थानों के बीच सरणियों / वर्गों / अभिलेखों को स्थानांतरित करना
एक विशिष्ट एन-बॉडी सिमुलेशन में, प्रत्येक युग के अंत में, प्रत्येक स्थान को दुनिया के अपने हिस्से (यानी सभी निकायों) को बाकी स्थानों पर साझा करने की आवश्यकता होगी । मैं इस पर स्थानीय-दृष्टिकोण (यानी on Loc
बयानों का उपयोग करके ) के साथ काम कर रहा हूं । मुझे कुछ अजीब व्यवहारों का सामना करना पड़ा, जिन्हें मैं समझ नहीं पाया, इसलिए मैंने एक परीक्षण कार्यक्रम बनाने का फैसला किया, जिसमें चीजें अधिक जटिल हो गईं। यहाँ प्रयोग को दोहराने के लिए कोड है।
proc log(args...?n) {
writeln("[locale = ", here.id, "] [", datetime.now(), "] => ", args);
}
const max: int = 50000;
record stuff {
var x1: int;
var x2: int;
proc init() {
this.x1 = here.id;
this.x2 = here.id;
}
}
class ctuff {
var x1: int;
var x2: int;
proc init() {
this.x1 = here.id;
this.x2 = here.id;
}
}
class wrapper {
// The point is that total size (in bytes) of data in `r`, `c` and `a` are the same here, because the record and the class hold two ints per index.
var r: [{1..max / 2}] stuff;
var c: [{1..max / 2}] owned ctuff?;
var a: [{1..max}] int;
proc init() {
this.a = here.id;
}
}
proc test() {
var wrappers: [LocaleSpace] owned wrapper?;
coforall loc in LocaleSpace {
on Locales[loc] {
wrappers[loc] = new owned wrapper();
}
}
// rest of the experiment further down.
}
दो दिलचस्प व्यवहार यहाँ होते हैं।
1. डेटा ले जाना
अब, wrapper
ऐरे में प्रत्येक उदाहरण को wrappers
उसके लोकेल में रहना चाहिए। विशेष रूप से, संदर्भ ( wrappers
) लोकेल 0 में रहते हैं, लेकिन आंतरिक डेटा ( r
, c
, a
) संबंधित स्थान में रहना चाहिए। इसलिए हम कुछ को लोकल 1 से लोकेल 3 तक ले जाने की कोशिश करते हैं, जैसे:
on Locales[3] {
var timer: Timer;
timer.start();
var local_stuff = wrappers[1]!.r;
timer.stop();
log("get r from 1", timer.elapsed());
log(local_stuff);
}
on Locales[3] {
var timer: Timer;
timer.start();
var local_c = wrappers[1]!.c;
timer.stop();
log("get c from 1", timer.elapsed());
}
on Locales[3] {
var timer: Timer;
timer.start();
var local_a = wrappers[1]!.a;
timer.stop();
log("get a from 1", timer.elapsed());
}
हैरानी की बात है, मेरे समय से पता चलता है कि
आकार (
const max
) के बावजूद , सरणी और रिकॉर्ड भेजने का समय स्थिर रहता है, जिससे मुझे कोई मतलब नहीं है। मैंने भी जाँच कीchplvis
, औरGET
वास्तव में आकार बढ़ता है, लेकिन समय एक जैसा रहता है।समय के साथ वर्ग क्षेत्र भेजने का समय बढ़ता है, जो समझ में आता है, लेकिन यह काफी धीमा है और मुझे नहीं पता कि यहां किस मामले में भरोसा करना है।
2. सीधे स्थानों को छोड़कर।
समस्या को ध्वस्त करने के लिए, मैंने .locale.id
कुछ चरों की सीधे क्वेरी की । सबसे पहले, हम डेटा की क्वेरी करते हैं, जिसे हम लोकल 2 में, लोकल 2 से जीने की उम्मीद करते हैं:
on Locales[2] {
var wrappers_ref = wrappers[2]!; // This is always 1 GET from 0, okay.
log("array",
wrappers_ref.a.locale.id,
wrappers_ref.a[1].locale.id
);
log("record",
wrappers_ref.r.locale.id,
wrappers_ref.r[1].locale.id,
wrappers_ref.r[1].x1.locale.id,
);
log("class",
wrappers_ref.c.locale.id,
wrappers_ref.c[1]!.locale.id,
wrappers_ref.c[1]!.x1.locale.id
);
}
और परिणाम है:
[locale = 2] [2020-12-26T19:36:26.834472] => (array, 2, 2)
[locale = 2] [2020-12-26T19:36:26.894779] => (record, 2, 2, 2)
[locale = 2] [2020-12-26T19:36:27.023112] => (class, 2, 2, 2)
जो अपेक्षित है। फिर भी, यदि हम लोकेल 1 पर उसी डेटा के स्थान को क्वेरी करते हैं, तो हमें यह मिलता है:
[locale = 1] [2020-12-26T19:34:28.509624] => (array, 2, 2)
[locale = 1] [2020-12-26T19:34:28.574125] => (record, 2, 2, 1)
[locale = 1] [2020-12-26T19:34:28.700481] => (class, 2, 2, 2)
कि यह दावा करना wrappers_ref.r[1].x1.locale.id
लोकेल 1 में रहता है, भले ही यह स्पष्ट रूप से स्थान 2 पर होना चाहिए । मेरा एकमात्र अनुमान है कि जब तक .locale.id
इसे निष्पादित किया जाता है, तब तक डेटा (अर्थात .x
रिकॉर्ड का) पहले से ही क्वेरी लोकेल (1) में चला जाता है।
इसलिए सभी में, पहले भाग का उत्तर न देते हुए, प्रयोग का दूसरा भाग एक गौण प्रश्न की ओर ले जाता है।
नोट: सभी प्रयोग के साथ चलाए जा रहे हैं -nl 4
में chapel/chapel-gasnet
डोकर छवि।
जवाब
अच्छी टिप्पणियों, मुझे देखने दो कि क्या मैं कुछ रोशनी बहा सकता हूं।
प्रारंभिक नोट के रूप में, गैसनेट डॉकर छवि के साथ लिया गया कोई भी समय नमक के एक दाने के साथ लिया जाना चाहिए, क्योंकि यह छवि चैपल में इच्छित प्रत्येक नोड पर अपने स्थानीय सिस्टम को चलाने के बजाय अपने स्थानीय सिस्टम का उपयोग करके कई नोड्स में निष्पादन का अनुकरण करता है। नतीजतन, यह वितरित मेमोरी कार्यक्रमों को विकसित करने के लिए उपयोगी है, लेकिन प्रदर्शन विशेषताओं को वास्तविक क्लस्टर या कंप्यूटर पर चलाने की तुलना में बहुत अलग होने की संभावना है। उस ने कहा, यह अभी भी मोटे समय के लिए उपयोगी हो सकता है (उदाहरण के लिए, आपका "यह बहुत लंबा समय ले रहा है" अवलोकन) या संचार का उपयोग करते हुए chplvis
या CommDiagnostics मॉड्यूल की गिनती के लिए ।
समय के बारे में आपकी टिप्पणियों के संबंध में, मैं यह भी देखता हूं कि सरणी का मामला बहुत धीमा है, और मुझे विश्वास है कि मैं कुछ व्यवहारों की व्याख्या कर सकता हूं:
सबसे पहले, यह समझना महत्वपूर्ण है कि किसी भी क्रॉस-नोड संचार की तरह सूत्र का उपयोग करके विशेषता की जा सकती है alpha + beta*length
। alpha
संचार के प्रदर्शन की मूल लागत का प्रतिनिधित्व करने के बारे में सोचो , लंबाई से स्वतंत्र। यह नेटवर्क पर आने के लिए सॉफ़्टवेयर स्टैक के माध्यम से कॉल करने की लागत का प्रतिनिधित्व करता है, डेटा को तार पर रख रहा है, इसे दूसरी तरफ प्राप्त कर रहा है, और इसे सॉफ्टवेयर स्टैक के माध्यम से वापस वहां एप्लिकेशन में ला रहा है। अल्फा का सटीक मूल्य संचार के प्रकार, सॉफ्टवेयर स्टैक की पसंद और भौतिक हार्डवेयर जैसे कारकों पर निर्भर करेगा। इस बीच, beta
संचार के प्रति-बाइट लागत का प्रतिनिधित्व करने के बारे में सोचें, जहां आप इंटुइट करते हैं, लंबे संदेशों में आवश्यक रूप से अधिक लागत होती है क्योंकि संचार को कैसे लागू किया जाता है, इसके आधार पर तार पर डालने के लिए अधिक डेटा या बफर या कॉपी करना संभव है।
मेरे अनुभव में, alpha
आमतौर पर beta
अधिकांश सिस्टम कॉन्फ़िगरेशन के लिए मूल्य हावी होता है। यह कहना नहीं है कि यह लंबे समय तक डेटा ट्रांसफ़र करने के लिए स्वतंत्र है, लेकिन निष्पादन समय में विचरण अधिक से अधिक छोटे बनाम स्थानान्तरण के लिए छोटा हो जाता है, क्योंकि यह एक एकल स्थानांतरण बनाम कई प्रदर्शन करने के लिए है। नतीजतन, जब n
तत्वों का एक हस्तांतरण बनाम n
1 तत्व के स्थानांतरण के बीच चयन किया जाता है , तो आप लगभग हमेशा पूर्व चाहते हैं।
आपके समय की जांच करने के लिए, मैंने आपके समयबद्ध कोड अंशों को CommDiagnostics
मॉड्यूल के लिए कॉल के साथ ब्रैकेट में डाल दिया है:
resetCommDiagnostics();
startCommDiagnostics();
...code to time here...
stopCommDiagnostics();
printCommDiagnosticsTable();
और पाया, जैसा कि आपने किया था chplvis
, कि रिकॉर्ड या सरणी के सरणी को स्थानीय बनाने के लिए आवश्यक संचार की संख्या स्थिर थी max
, उदाहरण के लिए, मैं :
स्थान | प्राप्त | निष्पादित करें |
---|---|---|
० | ० | ० |
1 | ० | ० |
२ | ० | ० |
३ | २१ | 1 |
यह कार्यान्वयन से मैं जो अपेक्षा करता हूं, उसके अनुरूप है: कि एक प्रकार के मान के लिए, हम सरणी मेटा-डेटा तक पहुंचने के लिए एक निश्चित संख्या में संचार करते हैं, और फिर एक एकल डेटा स्थानांतरण में सरणी तत्वों को संचार करने के लिए खुद को संचारित करते हैं। ओवरहेड्स (कई alpha
लागतों का भुगतान करने से बचें )।
इसके विपरीत, मैंने पाया कि वर्गों के सरणी को स्थानीय बनाने के लिए संचार की संख्या सरणी के आकार के लिए आनुपातिक थी। उदाहरण के लिए max
, मैंने 50,000 के डिफ़ॉल्ट मान के लिए देखा:
स्थान | प्राप्त | डाल दिया | निष्पादित करें |
---|---|---|---|
० | ० | ० | ० |
1 | ० | ० | ० |
२ | ० | ० | ० |
३ | 25040 है | 25000 | 1 |
मेरा मानना है कि इस अंतर का कारण इस तथ्य से संबंधित है कि कक्षाओं c
की एक सरणी है owned
, जिसमें एक बार में केवल एक ही वर्ग चर किसी दिए गए ctuff
ऑब्जेक्ट को "खुद" कर सकता है । परिणामस्वरूप, c
एक स्थान से दूसरे स्थान पर सरणी के तत्वों की प्रतिलिपि बनाते समय , आप केवल कच्चे डेटा की प्रतिलिपि नहीं बना रहे हैं, जैसा कि रिकॉर्ड और पूर्णांक मामलों के साथ, लेकिन प्रति तत्व एक स्वामित्व हस्तांतरण भी कर रहा है। यह अनिवार्य रूप nil
से स्थानीय वर्ग चर के लिए इसके मूल्य को कॉपी करने के बाद दूरस्थ मूल्य सेट करने की आवश्यकता है । हमारे वर्तमान कार्यान्वयन में, यह दूरस्थ get
दूरस्थ मान को स्थानीय एक की प्रतिलिपि बनाने के लिए रिमोट का उपयोग करके किया जाता है , इसके बाद put
रिमोट मान को सेट करने के लिए रिमोट का उपयोग किया जाता है nil
, इसलिए, हमारे पास एक सरणी तत्व है, जिसके परिणामस्वरूप हम प्राप्त करते हैं और डालते हैं। (n) पिछले मामलों की तरह O (1) के बजाय संचार। अतिरिक्त प्रयास के साथ, हम संभावित रूप से कंपाइलर को इस मामले को अनुकूलित कर सकते थे, हालांकि मेरा मानना है कि स्वामित्व हस्तांतरण करने की आवश्यकता के कारण यह हमेशा दूसरों की तुलना में अधिक महंगा होगा।
मैं अनुमान यह है कि परीक्षण किया owned
कक्षाओं अपने बदलकर अतिरिक्त भूमि के ऊपर है, जिसके परिणामस्वरूप कर रहे थे ctuff
होने से वस्तुओं owned
के लिए unmanaged
है, जो कार्यान्वयन से किसी भी स्वामित्व अर्थ विज्ञान निकाल देता है। जब मैं ऐसा करता हूं, तो मुझे लगातार संख्या में संचार दिखाई देते हैं, जैसा कि मूल्य के मामलों में होता है:
स्थान | प्राप्त | निष्पादित करें |
---|---|---|
० | ० | ० |
1 | ० | ० |
२ | ० | ० |
३ | २१ | 1 |
मेरा मानना है कि यह इस तथ्य का प्रतिनिधित्व करता है कि एक बार भाषा को वर्ग चर के स्वामित्व का प्रबंधन करने की कोई आवश्यकता नहीं होती है, यह बस एक बार फिर से अपने सूचक मूल्यों को एक हस्तांतरण में स्थानांतरित कर सकता है।
इन प्रदर्शन नोटों के अलावा, वर्गों और रिकॉर्डों के बीच एक महत्वपूर्ण अर्थ अंतर को समझना महत्वपूर्ण है जब उपयोग करना है। एक वर्ग ऑब्जेक्ट को ढेर पर आवंटित किया जाता है, और एक वर्ग चर अनिवार्य रूप से उस ऑब्जेक्ट के लिए एक संदर्भ या सूचक है। इस प्रकार, जब एक वर्ग चर को एक स्थान से दूसरे स्थान पर कॉपी किया जाता है, तो केवल पॉइंटर की प्रतिलिपि बनाई जाती है, और मूल वस्तु वही रहती है जहां वह थी (बेहतर या बदतर के लिए)। इसके विपरीत, एक रिकॉर्ड वैरिएबल स्वयं ऑब्जेक्ट का प्रतिनिधित्व करता है, और "स्थान पर" के रूप में आवंटित किए जाने के बारे में सोचा जा सकता है (उदाहरण के लिए, स्थानीय चर के लिए स्टैक पर)। जब एक रिकॉर्ड वैरिएबल को एक लोकल से दूसरे में कॉपी किया जाता है, तो यह ऑब्जेक्ट ही होता है (यानी, रिकॉर्ड के फील्ड्स वैल्यूज़) जो कॉपी किए जाते हैं, जिसके परिणामस्वरूप ऑब्जेक्ट की एक नई कॉपी ही बनती है। देखें इस तो सवाल यह अधिक जानकारी के लिए।
आपके दूसरे अवलोकन पर चलते हुए, मेरा मानना है कि आपकी व्याख्या सही है, और यह कार्यान्वयन में एक बग हो सकता है (मुझे आश्वस्त होने के लिए इस पर थोड़ा और स्टू करने की आवश्यकता है)। विशेष रूप से, मुझे लगता है कि आप सही हैं कि जो हो रहा है wrappers_ref.r[1].x1
उसका मूल्यांकन किया जा रहा है, जिसके परिणामस्वरूप स्थानीय चर में संग्रहीत किया जा रहा है, और यह कि .locale.id
स्थानीय क्षेत्र के बजाय परिणाम को संग्रहीत करने वाले स्थानीय चर पर क्वेरी लागू की जा रही है। मैंने इस सिद्धांत ref
को क्षेत्र में ले जाकर और फिर locale.id
उस रेफ की छपाई इस प्रकार से की:
ref x1loc = wrappers_ref.r[1].x1;
...wrappers_ref.c[1]!.x1.locale.id...
और यह सही परिणाम देने के लिए लग रहा था। मैंने उत्पन्न कोड को भी देखा जो यह दर्शाता था कि हमारे सिद्धांत सही थे। मुझे विश्वास नहीं है कि कार्यान्वयन को इस तरह से व्यवहार करना चाहिए, लेकिन आश्वस्त होने से पहले इसके बारे में थोड़ा और सोचने की आवश्यकता है। यदि आप चैपल के GitHub मुद्दों पृष्ठ पर इसके खिलाफ बग खोलना चाहते हैं , तो आगे की चर्चा के लिए, हम इसकी सराहना करेंगे।