सी++ के साथ जीपीयू प्रोग्रामिंग

Gpu Programming With C



इस गाइड में, हम C++ के साथ GPU प्रोग्रामिंग की शक्ति का पता लगाएंगे। डेवलपर्स सी ++ के साथ अविश्वसनीय प्रदर्शन की उम्मीद कर सकते हैं, और निम्न-स्तरीय भाषा के साथ जीपीयू की अभूतपूर्व शक्ति तक पहुंचने से वर्तमान में उपलब्ध कुछ सबसे तेज़ गणना मिल सकती है।

आवश्यकताएं

जबकि लिनक्स के आधुनिक संस्करण को चलाने में सक्षम कोई भी मशीन सी ++ कंपाइलर का समर्थन कर सकती है, आपको इस अभ्यास के साथ पालन करने के लिए एक एनवीआईडीआईए-आधारित जीपीयू की आवश्यकता होगी। यदि आपके पास GPU नहीं है, तो आप Amazon Web Services या अपनी पसंद के किसी अन्य क्लाउड प्रदाता में GPU-संचालित इंस्टेंस को स्पिन कर सकते हैं।







यदि आप एक भौतिक मशीन चुनते हैं, तो कृपया सुनिश्चित करें कि आपके पास NVIDIA के स्वामित्व वाले ड्राइवर स्थापित हैं। आप इसके लिए निर्देश यहां पा सकते हैं: https://linuxhint.com/install-nvidia-drivers-linux/



ड्राइवर के अलावा, आपको CUDA टूलकिट की आवश्यकता होगी। इस उदाहरण में, हम Ubuntu 16.04 LTS का उपयोग करेंगे, लेकिन अधिकांश प्रमुख वितरणों के लिए निम्न URL पर डाउनलोड उपलब्ध हैं: https://developer.nvidia.com/cuda-downloads



उबंटू के लिए, आप .deb आधारित डाउनलोड चुनेंगे। डाउनलोड की गई फ़ाइल में डिफ़ॉल्ट रूप से .deb एक्सटेंशन नहीं होगा, इसलिए मैं अनुशंसा करता हूं कि इसका नाम बदलकर अंत में .deb कर दिया जाए। फिर, आप इसके साथ स्थापित कर सकते हैं:





सुडो डीपीकेजी -मैंपैकेज-name.deb

आपको संभवतः एक GPG कुंजी स्थापित करने के लिए प्रेरित किया जाएगा, और यदि ऐसा है, तो ऐसा करने के लिए दिए गए निर्देशों का पालन करें।

एक बार ऐसा करने के बाद, अपने भंडार अपडेट करें:



सुडो उपयुक्त-अपडेट प्राप्त करें
सुडो उपयुक्त-स्थापित करेंचमत्कार-तथा

एक बार हो जाने के बाद, मैं यह सुनिश्चित करने के लिए रिबूट करने की सलाह देता हूं कि सब कुछ ठीक से लोड हो गया है।

GPU विकास के लाभ

सीपीयू कई अलग-अलग इनपुट और आउटपुट को संभालते हैं और इसमें न केवल प्रोग्राम की जरूरतों के विस्तृत वर्गीकरण से निपटने के लिए बल्कि अलग-अलग हार्डवेयर कॉन्फ़िगरेशन के प्रबंधन के लिए कार्यों का एक बड़ा वर्गीकरण होता है। वे मेमोरी, कैशिंग, सिस्टम बस, सेगमेंटिंग और आईओ कार्यक्षमता को भी संभालते हैं, जिससे वे सभी ट्रेडों का जैक बन जाते हैं।

जीपीयू इसके विपरीत हैं - उनमें कई व्यक्तिगत प्रोसेसर होते हैं जो बहुत ही सरल गणितीय कार्यों पर केंद्रित होते हैं। इस वजह से, वे सीपीयू की तुलना में कई गुना तेजी से कार्यों को संसाधित करते हैं। स्केलर फ़ंक्शंस (एक फ़ंक्शन जो एक या अधिक इनपुट लेता है लेकिन केवल एक आउटपुट देता है) में विशेषज्ञता के द्वारा, वे अत्यधिक विशेषज्ञता की कीमत पर चरम प्रदर्शन प्राप्त करते हैं।

उदाहरण कोड

उदाहरण कोड में, हम वैक्टर को एक साथ जोड़ते हैं। मैंने गति तुलना के लिए कोड का एक सीपीयू और जीपीयू संस्करण जोड़ा है।
GPU-example.cpp नीचे दी गई सामग्री:

#शामिल 'cuda_runtime.h'
#शामिल
#शामिल
#शामिल
#शामिल
#शामिल

टाइपडीफघंटे::chrono::उच्च_रिज़ॉल्यूशन_घड़ीघड़ी;

#परिभाषित करें ITER 65535

// वेक्टर ऐड फंक्शन का सीपीयू वर्जन
शून्यवेक्टर_एड_सीपीयू(NS *प्रति,NS *बी,NS *सी,NSएन) {
NSमैं;

// वेक्टर तत्वों a और b को वेक्टर c . में जोड़ें
के लिये (मैं= 0;मैं<एन; ++मैं) {
सी[मैं] =प्रति[मैं] +बी[मैं];
}
}

// वेक्टर ऐड फंक्शन का GPU संस्करण
__वैश्विक__शून्यवेक्टर_एड_जीपीयू(NS *जीपीयू_ए,NS *जीपीयू_बी,NS *जीपीयू_सी,NSएन) {
NSमैं=थ्रेड आईडीएक्स।एक्स;
// लूप के लिए आवश्यक नहीं है क्योंकि CUDA रनटाइम
// इस ITER बार को थ्रेड करेगा
जीपीयू_सी[मैं] =जीपीयू_ए[मैं] +जीपीयू_बी[मैं];
}

NSमुख्य() {

NS *प्रति,*बी,*सी;
NS *जीपीयू_ए,*जीपीयू_बी,*जीपीयू_सी;

प्रति= (NS *)मॉलोक(आईटीईआर* का आकार(NS));
बी= (NS *)मॉलोक(आईटीईआर* का आकार(NS));
सी= (NS *)मॉलोक(आईटीईआर* का आकार(NS));

// हमें GPU के लिए सुलभ चर की आवश्यकता है,
// इसलिए cudaMallocManaged ये प्रदान करता है
cudaMalloc प्रबंधित(औरजीपीयू_ए, आईटीईआर* का आकार(NS));
cudaMalloc प्रबंधित(औरजीपीयू_बी, आईटीईआर* का आकार(NS));
cudaMalloc प्रबंधित(औरजीपीयू_सी, आईटीईआर* का आकार(NS));

के लिये (NSमैं= 0;मैं<आईटीईआर; ++मैं) {
प्रति[मैं] =मैं;
बी[मैं] =मैं;
सी[मैं] =मैं;
}

// सीपीयू फ़ंक्शन को कॉल करें और इसे समय दें
ऑटोसीपीयू_स्टार्ट=घड़ी::अभी();
वेक्टर_एड_सीपीयू(ए, बी, सी, आईटीईआर);
ऑटोसीपीयू_एंड=घड़ी::अभी();
घंटे::लागत << 'वेक्टर_एड_सीपीयू:'
<<घंटे::chrono::अवधि_कास्ट<घंटे::chrono::नैनोसेकंड>(सीपीयू_एंड-सीपीयू_स्टार्ट).गिनती()
<< ' नैनोसेकंड।एन';

// GPU फ़ंक्शन को कॉल करें और इसे समय दें
// ट्रिपल एंगल ब्रेकेट एक CUDA रनटाइम एक्सटेंशन है जो अनुमति देता है
// एक CUDA कर्नेल कॉल के मापदंडों को पारित किया जाना है।
// इस उदाहरण में, हम ITER थ्रेड्स के साथ एक थ्रेड ब्लॉक पास कर रहे हैं।
ऑटोजीपीयू_स्टार्ट=घड़ी::अभी();
वेक्टर_एड_जीपीयू<<<1, आईटीईआर>>> (जीपीयू_ए, जीपीयू_बी, जीपीयू_सी, आईटीईआर);
cudaDevice सिंक्रनाइज़ करें();
ऑटोजीपीयू_एंड=घड़ी::अभी();
घंटे::लागत << 'वेक्टर_एड_जीपीयू:'
<<घंटे::chrono::अवधि_कास्ट<घंटे::chrono::नैनोसेकंड>(जीपीयू_एंड-जीपीयू_स्टार्ट).गिनती()
<< ' नैनोसेकंड।एन';

// GPU-फ़ंक्शन आधारित मेमोरी आवंटन मुक्त करें
कुडाफ्री(प्रति);
कुडाफ्री(बी);
कुडाफ्री(सी);

// सीपीयू-फ़ंक्शन आधारित मेमोरी आवंटन मुक्त करें
नि: शुल्क(प्रति);
नि: शुल्क(बी);
नि: शुल्क(सी);

वापसी 0;
}

मेकफ़ाइल नीचे दी गई सामग्री:

कांग्रेस=-मैं/usr/स्थानीय/चमत्कार/शामिल
एनवीसीसी=/usr/स्थानीय/चमत्कार/पूर्वाह्न/एनवीसीसी
एनवीसीसी_ओपीटी=-एसटीडी=सी++ग्यारह

सब:
$(एनवीसीसी)$(एनवीसीसी_ओपीटी)GPU-example.cpp-याजीपीयू-उदाहरण

साफ:
-आरएम -एफजीपीयू-उदाहरण

उदाहरण चलाने के लिए, इसे संकलित करें:

बनाना

फिर प्रोग्राम चलाएँ:

./जीपीयू-उदाहरण

जैसा कि आप देख सकते हैं, CPU संस्करण (vector_add_cpu) GPU संस्करण (vector_add_gpu) की तुलना में काफी धीमा चलता है।

यदि नहीं, तो आपको gpu-example.cu में ITER परिभाषित को अधिक संख्या में समायोजित करने की आवश्यकता हो सकती है। यह GPU सेटअप समय के कारण कुछ छोटे CPU-गहन लूप से अधिक लंबा है। मुझे अपनी मशीन पर अच्छा काम करने के लिए ६५५३५ मिले, लेकिन आपका माइलेज भिन्न हो सकता है। हालाँकि, एक बार जब आप इस सीमा को साफ़ कर लेते हैं, तो GPU CPU की तुलना में नाटकीय रूप से तेज़ हो जाता है।

निष्कर्ष

मुझे आशा है कि आपने C++ के साथ GPU प्रोग्रामिंग में हमारे परिचय से बहुत कुछ सीखा है। ऊपर दिया गया उदाहरण बहुत कुछ पूरा नहीं करता है, लेकिन प्रदर्शित अवधारणाएं एक ढांचा प्रदान करती हैं जिसका उपयोग आप अपने जीपीयू की शक्ति को मुक्त करने के लिए अपने विचारों को शामिल करने के लिए कर सकते हैं।