2012-04-04 26 views
5

मैं एक ऐसा प्रोग्राम तैयार कर रहा हूं जिसे अल्ट्रा-फास्ट होना चाहिए। यह सीयूडीए का उपयोग कर जीपीयू पर कुछ सामान चला रहा है और बाद में यह सीपीयू पर कुछ गणना करता है। इसके लिए, मुझे अत्यधिक अनुकूलित जीपीयू-डेटास्ट्रक्चर को उस चीज़ पर परिवर्तित करने की आवश्यकता है जिसे मैं आसानी से सीपीयू पर उपयोग कर सकता हूं। मेरा डेटा मूल रूप से एक ग्रिड में एक ग्राफ रखा गया है। वर्तमान में मैं सीपीयू भाग के लिए std :: वेक्टर का उपयोग कर रहा हूं।std :: वेक्टर बनाम सामान्य सरणी

new_graph.resize(blockSize * blockSize); 
for (unsigned long long y = 0; y < blockSize; y++) { 
    for (unsigned long long x = 0; x < blockSize; x++) { 
     int idx = y * blockSize + x; 
     new_graph[idx] = Vertex(x, y); 
    } 
} 

बाद में: क्योंकि मैं जानता हूँ कि वहाँ काफी एक ओवरहेड है अगर मैं push_back() रों का एक बहुत करते हैं और मैं कम से कम क्योंकि मैं जानता हूँ कि कितने कोने मैं अपने ग्राफ में है पता है, मैं अब इस के लिए निम्नलिखित कोड का उपयोग मैं किनारों को जोड़ता हूँ। दुर्भाग्यवश मुझे नहीं पता कि मेरे पास प्रति चरम कितने किनारे हैं, लेकिन मुझे पता है कि यह कभी भी 8 से बड़ा नहीं होगा। इसलिए मैं प्रत्येक std :: वेक्टर में reserve() 8 जो किनारों के लिए उपयोग करता हूं।

हालांकि, यह दोनों बेहद धीमे प्रतीत होते हैं। यदि मैं ग्राफ के लिए एक सामान्य सरणी का उपयोग करता हूं (इसलिए मूल रूप से बाहरी std :: वेक्टर को प्रतिस्थापित करना), उस भाग में गति सुधार बहुत बड़ा है (जैसे 10x या तो)।

ग्राफ के लिए यह करने योग्य है, लेकिन किनारों के लिए वास्तव में नहीं, क्योंकि मैं इन किनारों पर कुछ पोस्ट-प्रोसेसिंग करता हूं और इसके लिए मुझे वास्तव में std :: वेक्टर की तरह कुछ चाहिए जो थोड़े गतिशील है (मैं कुछ किनारों को जोड़ता हूं) ।

वर्तमान में डेटा को std :: vector में कनवर्ट करना GPU (जो एक स्मार्ट एमएसटी एल्गोरिदम है) पर मेरे एल्गोरिदम चलाने से 10 गुना धीमा है। यह वास्तव में मैं नहीं चाहता हूं, क्योंकि अब ओवरहेड रास्ता बहुत बड़ा है।

क्या कोई जानता है कि क्या हो रहा है या मैं इसे कैसे ठीक कर सकता हूं?

पेज। मैं -ओ 2 के साथ संकलित करता हूं, क्योंकि मुझे पहले ही पता चला है कि इससे बड़ा अंतर हो सकता है। ओओ 3 के साथ भी कोशिश की, कोई वास्तविक अंतर नहीं।

वर्टेक्स इस प्रकार परिभाषित किया गया है:

struct Pos { 
    int x, y; 
    Pos() { 
     x = 0; 
     y = 0; 
    } 

    Pos(int x, int y) { 
     this->x = x; 
     this->y = y; 
    } 
}; 

struct Vertex { 
    Pos pos; 
    bool hidden; 
    unsigned long long newIdx; 
    Vertex() { 
     this->pos = Pos(); 
     this->hidden = false; 
     this->numEdges = 0; 
     this->numRemovedEdges = 0; 
    } 

    Vertex(Pos &pos) { 
     this->pos = pos; 
     this->hidden = false; 
     this->numEdges = 0; 
     this->numRemovedEdges = 0; 
    } 

    Vertex(int x, int y) { 
     this->pos = Pos(x, y); 
     this->hidden = false; 
     this->numEdges = 0; 
     this->numRemovedEdges = 0; 
    } 
    int numEdges; 
    int numRemovedEdges; 
    std::vector<Edge> edges; 
    std::vector<bool> removed; 
    std::vector<bool> doNotWrite; 
}; 
+0

'-O3' के साथ संकलित करने का प्रयास करें जो कुछ फ़ंक्शंस को रेखांकित करेगा (99.9 99% मौका यह 'push_back' इनलाइन करेगा, और यदि यह तब नहीं होता है तो कार्यान्वयन या कंपाइलर बकवास का एक टुकड़ा है)। –

+0

@daknok_t ने भी कोशिश की, कोई वास्तविक अंतर नहीं। – nickygerritsen

+1

'आकार बदलें' के बजाय 'रिजर्व' को कॉल करना और फिर '[]' के बजाय 'push_back' का उपयोग करना 'आकार बदलें' द्वारा किए गए अनावश्यक प्रारंभिकरण से बच जाएगा। मुझे नहीं पता कि यह 10x मंदी का कारण है (मुझे संदेह है कि यह सब कुछ के लिए खाता है), लेकिन यह निश्चित रूप से मदद करनी चाहिए। –

उत्तर

3

शायद आप गतिशील स्मृति आवंटन के लिए भुगतान कर रहे हैं कि vector अपने तत्वों के लिए स्थान आरक्षित करने के लिए करता है?

आप बेहतर, आप प्रत्येक के लिए कम से कम 3 स्मृति आवंटन और हर Vertex (edges के लिए एक, एक removed के लिए और doNotWrite के लिए एक) होगा reserve यहां तक ​​कि अगर। डायनामिक मेमोरी आवंटन संभावित रूप से महंगा है जो उच्च प्रदर्शन सामग्री से संबंधित है जो आप यहां करने का प्रयास कर रहे हैं।

या तो अपने पुराने ज़रूरतों के अनुरूप, vector के साथ पर्याप्त रूप से पर्याप्त (संभावित रूप से बर्बाद करने वाली जगह) या एक विशेष मेमोरी आवंटक के साथ सादे पुराने सरणी का उपयोग करें।


इसके अलावा, क्या आप स्मृति क्रम में तत्वों तक पहुंचते हैं? आपका उदाहरण ऐसा सुझाव देता है, लेकिन क्या आप इसे सभी मामलों में करते हैं?


इसके अलावा, आपको Vertex.pos की भी आवश्यकता है? क्या यह अनुमानित ग्रिड में Vertex की स्थिति से नहीं हो सकता है?

+0

अब मैं सादे पुराने सरणी पर काम कर रहा हूं, सोचो कि इससे कोई फर्क पड़ेगा। मैं हमेशा क्रम में उन्हें एक्सेस नहीं करता हूं और Vertex.pos आवश्यक है क्योंकि बाद में मैं अपनी संरचना से नोड्स हटा देता हूं, इसलिए मैं अब ग्रिड की स्थिति का उपयोग नहीं कर सकता। – nickygerritsen

+0

अंत में मैंने अपनी खुद की सरणी बनाने का फैसला किया, जिसने – nickygerritsen

0

आप एक वर्टेक्स वस्तु नहीं बना सकता, इसे में x और y मान memcpy (ताकि आप प्रत्येक पाश के लिए निर्माता को फोन करने की जरूरत नहीं है) , फिर पूरे वर्टेक्स को अपने std :: वेक्टर में memcpy? वेक्टर की स्मृति को नियमित सरणी की तरह निर्धारित करने की गारंटी दी जाती है, ताकि आप सभी अमूर्तता को बाईपास कर सकें और सीधे मेमोरी का उपयोग कर सकें। जटिल सामान की कोई ज़रूरत नहीं है। साथ ही, आप GPU से वापस प्राप्त डेटा को लेआउट कर सकते हैं ताकि आप एक ही बार में पूरे ब्लॉक को याद कर सकें, और भी आपको बचा सकें।

+0

धन्यवाद मैं कल यह करने की कोशिश करूंगा :)। – nickygerritsen

1

एक और समाधान है जिसे मैंने हाल ही में इसी तरह की स्थिति में उपयोग किया था। llvm पैकेज में SmallVector क्लास है। यह इंटरफ़ेस प्रदान करता है जो std :: वेक्टर के समान है, लेकिन यह कुछ निश्चित संख्या में तत्वों को ऑनलाइन रखने की अनुमति देता है (इसलिए जब तक वेक्टर प्रारंभिक सीमा से ऊपर नहीं बढ़ता है, कोई अतिरिक्त स्मृति आवंटन नहीं होता है)। यदि छोटे वेक्टर उस प्रारंभिक आकार से ऊपर बढ़ने की कोशिश करता है तो स्मृति ब्लॉक आवंटित किया जाता है, और सभी आइटम वहां स्थानांतरित होते हैं - सभी एक पारदर्शी चरण में।

कुछ बातें है कि मैं इस SmallVector में ठीक करने के लिए किया था:

  1. मदों की सबसे छोटी संख्या है कि यथा-स्थान लगाया जा सकता 2 जब 1 आइटम उदा में इस्तेमाल किया जाता है, इसलिए 99.99% मामलों वहाँ एक काफी भूमि के ऊपर
  2. स्वैप() मुक्त स्मृति को (SmallVector()। स्वैप (vec)) का सामान्य उपयोग करता है मुक्त स्मृति नहीं है, इसलिए मैं इसे अपने आप को लागू करने के लिए किया था

बस देखो छोटे वेक्टर वर्ग

1

के स्रोत कोड के लिए llvm के नवीनतम संस्करण के लिए अपरिवर्तनीय स्मृति आवंटन, अनावश्यक असाइनमेंट ऑपरेशंस, और प्रत्येक Vertex के समग्र आकार की संख्या के कारण CPU डेटा संरचना अत्यंत अक्षम है। इस संरचना को अनुकूलित करने पर विचार करने से पहले सीपीयू डेटा संरचनाओं और जीपीयू डेटा संरचनाओं के बीच डेटा प्रवाह को समझना अच्छा होगा क्योंकि दो प्रारूपों के बीच रूपांतरण में काफी समय लग सकता है। यह सवाल पूछता है, CPU पक्ष पर GPU संरचना का उपयोग क्यों नहीं किया जाता है?

यदि आप केवल इसे सीपीयू पक्ष से देख रहे थे और आप एओएस डेटा संरचना को बनाए रखना चाहते हैं तो 1. वर्टेक्स डेटा संरचना को सरल बनाएं। 2. सभी गतिशील स्मृति आवंटन को हटाएं। प्रत्येक std :: वेक्टर एक dynb करेगा 3. हटाएं और doNotWrite को std :: bitset < 8> पर बदलें। 4. numRemoveEdges निकालें। यह remove.count() है। 5. यदि एज छोटा है तो आप एज किनारों को घोषित करने के लिए तेज़ी से पा सकते हैं [8]। 6. यदि आप वेक्टर के साथ रहने का फैसला करते हैं तो पूल आवंटक का उपयोग करने पर विचार करें। 7. Vertex के आकार को कम करने के लिए Vertex में डेटा तत्वों को पुन: व्यवस्थित करें।

इन सभी सिफारिशों में GPU के साथ डेटा साझा करने का सबसे अच्छा समाधान नहीं है। यदि आप पूल आवंटक का उपयोग करते हैं और आप यूवीए (सीयूडीए लिनक्स) का उपयोग करते हैं तो आप डेटा को एक ही मेमोरी प्रति के साथ जीपीयू में कॉपी कर सकते हैं।

+0

की गति में सुधार किया, सुझावों के लिए धन्यवाद, इसमें से कुछ कोशिश करेंगे। – nickygerritsen

संबंधित मुद्दे