क्या vector<vector<double>>
उच्च प्रदर्शन वैज्ञानिक कंप्यूटिंग कोड के लिए मैट्रिक्स क्लास बनाने के लिए (std का उपयोग करना) एक अच्छा विचार है ?
अगर जवाब नहीं है। क्यूं कर? धन्यवाद
क्या vector<vector<double>>
उच्च प्रदर्शन वैज्ञानिक कंप्यूटिंग कोड के लिए मैट्रिक्स क्लास बनाने के लिए (std का उपयोग करना) एक अच्छा विचार है ?
अगर जवाब नहीं है। क्यूं कर? धन्यवाद
जवाबों:
यह एक बुरा विचार है क्योंकि वेक्टर को अंतरिक्ष में कई वस्तुओं को आवंटित करने की आवश्यकता होती है क्योंकि आपके मैट्रिक्स में पंक्तियाँ होती हैं। आवंटन महंगा है, लेकिन मुख्य रूप से यह एक बुरा विचार है क्योंकि आपके मैट्रिक्स का डेटा अब मेमोरी के चारों ओर बिखरे हुए कई सरणियों में मौजूद है, बजाय सभी जगह एक जगह जहां प्रोसेसर कैश आसानी से इसे एक्सेस कर सकता है।
यह एक बेकार भंडारण प्रारूप भी है: एसटीडी :: वेक्टर दो बिंदुओं को संग्रहीत करता है, एक सरणी की शुरुआत में और एक अंत तक क्योंकि सरणी की लंबाई लचीली होती है। दूसरी ओर, इसके लिए एक उचित मैट्रिक्स होने के लिए, सभी पंक्तियों की लंबाई समान होनी चाहिए और इसलिए प्रत्येक पंक्ति को अपनी लंबाई को स्वतंत्र रूप से संग्रहीत करने की बजाय केवल एक बार स्तंभों की संख्या को संग्रहीत करना पर्याप्त होगा।
std::vector
वास्तव में तीन बिंदुओं को संग्रहीत करता है: शुरुआत, अंत, और आवंटित भंडारण क्षेत्र का अंत (हमें कॉल करने की अनुमति देता है, उदाहरण के लिए .capacity()
)। वह क्षमता आकार से भिन्न हो सकती है जिससे स्थिति बहुत अधिक खराब हो जाती है!
वुल्फगैंग ने जिन कारणों का उल्लेख किया है, यदि आप एक का उपयोग करते हैं, तो इसके अलावा vector<vector<double> >
, आपको हर बार एक तत्व को पुनः प्राप्त करना चाहते हैं, जो कि एक dereferencing ऑपरेशन की तुलना में अधिक कम्प्यूटेशनल रूप से महंगा है। एक विशिष्ट दृष्टिकोण इसके बजाय एक एकल सरणी ( vector<double>
या ए double *
) आवंटित करना है। मैंने यह भी देखा है कि लोग उचित सूचकांकों को लागू करने के लिए आवश्यक "मानसिक उपरि" की मात्रा को कम करने के लिए इस एकल सरणी के चारों ओर लपेटकर मैट्रिक्स कक्षाओं में सिंथैटिक शुगर जोड़ते हैं।
नहीं, मुफ्त उपलब्ध रैखिक बीजगणित पुस्तकालयों में से एक का उपयोग करें। विभिन्न पुस्तकालयों के बारे में एक चर्चा यहाँ मिल सकती है: एक प्रयोग करने योग्य, तेज़ C ++ मैट्रिक्स लाइब्रेरी के लिए सिफारिशें?
क्या वाकई इतनी बुरी बात है?
@Wolfgang: घने मैट्रिक्स के आकार के आधार पर, प्रति पंक्ति दो अतिरिक्त पॉइंटर लापरवाही योग्य हो सकते हैं। बिखरे हुए डेटा के संबंध में, कोई कस्टम आवंटनकर्ता का उपयोग करने के बारे में सोच सकता है जो यह सुनिश्चित करता है कि वैक्टर सन्निहित स्मृति में हैं। जब तक मेमोरी को पुनर्नवीनीकरण नहीं किया जाता है तब भी मानक एलोकेटर दो पॉइंटर आकार के अंतराल के साथ सन्निहित मेमोरी करेगा।
@Geoff: यदि आप रैंडम एक्सेस कर रहे हैं और सिर्फ एक एरे का उपयोग करें तो आपको अभी भी इंडेक्स की गणना करनी होगी। तेज नहीं हो सकता।
तो आइए हम एक छोटी सी परीक्षा करें:
vectormatrix.cc:
#include<vector>
#include<iostream>
#include<random>
#include <functional>
#include <sys/time.h>
int main()
{
int N=1000;
struct timeval start, end;
std::cout<< "Checking differenz between last entry of previous row and first entry of this row"<<std::endl;
std::vector<std::vector<double> > matrix(N, std::vector<double>(N, 0.0));
for(std::size_t i=1; i<N;i++)
std::cout<< "index "<<i<<": "<<&(matrix[i][0])-&(matrix[i-1][N-1])<<std::endl;
std::cout<<&(matrix[0][N-1])<<" "<<&(matrix[1][0])<<std::endl;
gettimeofday(&start, NULL);
int k=0;
for(int j=0; j<100; j++)
for(std::size_t i=0; i<N;i++)
for(std::size_t j=0; j<N;j++, k++)
matrix[i][j]=matrix[i][j]*matrix[i][j];
gettimeofday(&end, NULL);
double seconds = end.tv_sec - start.tv_sec;
double useconds = end.tv_usec - start.tv_usec;
double mtime = ((seconds) * 1000 + useconds/1000.0) + 0.5;
std::cout<<"calc took: "<<mtime<<" k="<<k<<std::endl;
std::normal_distribution<double> normal_dist(0, 100);
std::mt19937 engine; // Mersenne twister MT19937
auto generator = std::bind(normal_dist, engine);
for(std::size_t i=1; i<N;i++)
for(std::size_t j=1; j<N;j++)
matrix[i][j]=generator();
}
और अब एक सरणी का उपयोग कर:
arraymatrix.cc
#include<vector>
#include<iostream>
#include<random>
#include <functional>
#include <sys/time.h>
int main()
{
int N=1000;
struct timeval start, end;
std::cout<< "Checking difference between last entry of previous row and first entry of this row"<<std::endl;
double* matrix=new double[N*N];
for(std::size_t i=1; i<N;i++)
std::cout<< "index "<<i<<": "<<(matrix+(i*N))-(matrix+(i*N-1))<<std::endl;
std::cout<<(matrix+N-1)<<" "<<(matrix+N)<<std::endl;
int NN=N*N;
int k=0;
gettimeofday(&start, NULL);
for(int j=0; j<100; j++)
for(double* entry =matrix, *endEntry=entry+NN;
entry!=endEntry;++entry, k++)
*entry=(*entry)*(*entry);
gettimeofday(&end, NULL);
double seconds = end.tv_sec - start.tv_sec;
double useconds = end.tv_usec - start.tv_usec;
double mtime = ((seconds) * 1000 + useconds/1000.0) + 0.5;
std::cout<<"calc took: "<<mtime<<" k="<<k<<std::endl;
std::normal_distribution<double> normal_dist(0, 100);
std::mt19937 engine; // Mersenne twister MT19937
auto generator = std::bind(normal_dist, engine);
for(std::size_t i=1; i<N*N;i++)
matrix[i]=generator();
}
मेरे सिस्टम पर अब स्पष्ट विजेता (-ओ 3 के साथ कंपाइलर जीसीसी 4.7) है
समय vectormatrix प्रिंट:
index 997: 3
index 998: 3
index 999: 3
0xc7fc68 0xc7fc80
calc took: 185.507 k=100000000
real 0m0.257s
user 0m0.244s
sys 0m0.008s
हम यह भी देखते हैं, कि जब तक मानक आवंटन मुक्त मेमोरी को रीसायकल नहीं करता है, तब तक डेटा सन्निहित है। (निश्चित रूप से कुछ सौदे के बाद इसके लिए कोई गारंटी नहीं है।)
समय सरणी मैट्रिक्स:
index 997: 1
index 998: 1
index 999: 1
0x7ff41f208f48 0x7ff41f208f50
calc took: 187.349 k=100000000
real 0m0.257s
user 0m0.248s
sys 0m0.004s
मैं इसकी अनुशंसा नहीं करता, लेकिन प्रदर्शन के मुद्दों के कारण नहीं। यह एक पारंपरिक मैट्रिक्स की तुलना में थोड़ा कम प्रदर्शन करने वाला होगा, जो आमतौर पर एक संकेतक सूचक और पूर्णांक अंकगणित का उपयोग करके अनुक्रमित डेटा के एक बड़े भाग के रूप में आवंटित किया जाता है। प्रदर्शन के हिट होने का कारण ज्यादातर कैशिंग मतभेद हैं, लेकिन एक बार जब आपके मैट्रिक्स का आकार काफी बड़ा हो जाता है तो यह प्रभाव बढ़ जाएगा और यदि आप आंतरिक वैक्टर के लिए एक विशेष आवंटन का उपयोग करते हैं ताकि वे कैश सीमाओं से जुड़ जाएं तो यह कैशिंग मुद्दे को कम कर देता है ।
मेरे विचार से ऐसा नहीं करना ही कारण है। मेरे लिए इसका कारण यह है कि यह बहुत सारे कोडिंग सिरदर्द पैदा करता है। यहां सिरदर्द की एक सूची दी गई है, जिससे यह दीर्घकालिक होगा
यदि आप अधिकांश एचपीसी पुस्तकालयों का उपयोग करना चाहते हैं, तो आपको अपने वेक्टर पर पुनरावृत्ति करने और अपने सभी डेटा को एक सन्निहित बफर में रखने की आवश्यकता होगी, क्योंकि अधिकांश एचपीसी पुस्तकालय इस स्पष्ट प्रारूप की अपेक्षा करते हैं। BLAS और LAPACK का ख्याल आता है, लेकिन सर्वव्यापी HPC लाइब्रेरी MPI का उपयोग करना बहुत कठिन होगा।
std::vector
इसकी प्रविष्टियों के बारे में कुछ भी नहीं पता है। यदि आप std::vector
अधिक std::vector
एस के साथ भरते हैं, तो यह पूरी तरह से आपका काम है कि यह सुनिश्चित करें कि उन सभी का आकार समान है, क्योंकि याद रखें कि हम एक मैट्रिक्स चाहते हैं और मैट्रिक्स में पंक्तियों की संख्या (या कॉलम) नहीं है। इस प्रकार आपको अपने बाहरी वेक्टर की हर प्रविष्टि के लिए सभी सही कंस्ट्रक्टरों को कॉल करना होगा, और आपके कोड का उपयोग std::vector<T>::push_back()
करने वाले किसी भी व्यक्ति को किसी भी आंतरिक वैक्टर पर उपयोग करने के लिए प्रलोभन का विरोध करना होगा, जिससे निम्नलिखित सभी कोड टूट जाएंगे। यदि आप अपनी कक्षा को सही ढंग से लिखते हैं, तो बेशक आप इसे अस्वीकार कर सकते हैं, लेकिन इसे बड़े सन्निहित आवंटन के साथ लागू करना बहुत आसान है।
एचपीसी प्रोग्रामर केवल निम्न स्तर के डेटा की उम्मीद करते हैं। यदि आप उन्हें एक मैट्रिक्स देते हैं, तो एक उम्मीद होती है कि यदि वे मैट्रिक्स के पहले तत्व के लिए पॉइंटर को पकड़ते हैं और मैट्रिक्स के अंतिम तत्व को एक पॉइंटर करते हैं, तो इन दोनों के बीच के सभी संकेत मान्य हैं और उसी के तत्वों को इंगित करते हैं मैट्रिक्स। यह मेरे पहले बिंदु के समान है, लेकिन अलग है क्योंकि यह पुस्तकालयों से संबंधित नहीं हो सकता है, बल्कि टीम के सदस्यों या किसी के साथ आप अपना कोड साझा करते हैं।
आपके वांछित डेटा संरचना के निम्नतम स्तर के प्रतिनिधित्व को छोड़ने से एचपीसी के लिए लंबे समय में आपका जीवन आसान हो जाता है। जैसे टूल का उपयोग करना perf
और vtune
आपको बहुत ही निम्न स्तर का प्रदर्शन काउंटर माप देगा जो आप अपने कोड के प्रदर्शन को बेहतर बनाने के लिए पारंपरिक प्रोफाइलिंग परिणामों के साथ संयोजन करने का प्रयास करेंगे। यदि आपकी डेटा संरचना बहुत सारे फैंसी कंटेनरों का उपयोग करती है, तो यह समझना मुश्किल होगा कि कैश की कमी कंटेनर या एल्गोरिथ्म में अक्षमता के साथ एक समस्या से आ रही है। अधिक जटिल कोड कंटेनरों के लिए आवश्यक हैं, लेकिन मैट्रिक्स बीजगणित के लिए वे वास्तव में नहीं हैं - आप केवल 1
std::vector
डेटा को स्टोर करने के बजाय n
std::vector
एस के साथ मिल सकते हैं , इसलिए उसके साथ जाएं।
मैं एक बेंचमार्क भी लिखता हूं। छोटे आकार के मैट्रिक्स के लिए (<100 * 100), प्रदर्शन वेक्टर <वेक्टर <डबल >> और लिपटे 1 डी वेक्टर के लिए समान है। बड़े आकार के मैट्रिक्स के लिए (~ 1000 * 1000), लिपटे 1D वेक्टर बेहतर है। Eigen मैट्रिक्स बदतर व्यवहार करता है। यह मेरे लिए आश्चर्य की बात है कि ईजन सबसे खराब है।
#include <iostream>
#include <iomanip>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <map>
#include <vector>
#include <string>
#include <cmath>
#include <numeric>
#include "time.h"
#include <chrono>
#include <cstdlib>
#include <Eigen/Dense>
using namespace std;
using namespace std::chrono; // namespace for recording running time
using namespace Eigen;
int main()
{
const int row = 1000;
const int col = row;
const int N = 1e8;
// 2D vector
auto start = high_resolution_clock::now();
vector<vector<double>> vec_2D(row,vector<double>(col,0.));
for (int i = 0; i < N; i++)
{
for (int i=0; i<row; i++)
{
for (int j=0; j<col; j++)
{
vec_2D[i][j] *= vec_2D[i][j];
}
}
}
auto stop = high_resolution_clock::now();
auto duration = duration_cast<microseconds>(stop - start);
cout << "2D vector: " << duration.count()/1e6 << " s" << endl;
// 2D array
start = high_resolution_clock::now();
double array_2D[row][col];
for (int i = 0; i < N; i++)
{
for (int i=0; i<row; i++)
{
for (int j=0; j<col; j++)
{
array_2D[i][j] *= array_2D[i][j];
}
}
}
stop = high_resolution_clock::now();
duration = duration_cast<microseconds>(stop - start);
cout << "2D array: " << duration.count() / 1e6 << " s" << endl;
// wrapped 1D vector
start = high_resolution_clock::now();
vector<double> vec_1D(row*col, 0.);
for (int i = 0; i < N; i++)
{
for (int i=0; i<row; i++)
{
for (int j=0; j<col; j++)
{
vec_1D[i*col+j] *= vec_1D[i*col+j];
}
}
}
stop = high_resolution_clock::now();
duration = duration_cast<microseconds>(stop - start);
cout << "1D vector: " << duration.count() / 1e6 << " s" << endl;
// eigen 2D matrix
start = high_resolution_clock::now();
MatrixXd mat(row, col);
for (int i = 0; i < N; i++)
{
for (int j=0; j<col; j++)
{
for (int i=0; i<row; i++)
{
mat(i,j) *= mat(i,j);
}
}
}
stop = high_resolution_clock::now();
duration = duration_cast<microseconds>(stop - start);
cout << "2D eigen matrix: " << duration.count() / 1e6 << " s" << endl;
}
जैसा कि दूसरों ने बताया है, इसके साथ गणित करने या कुछ भी करने की कोशिश मत करो।
उस ने कहा, मैंने इस संरचना को एक अस्थायी के रूप में उपयोग किया है जब कोड को 2-डी सरणी को इकट्ठा करने की आवश्यकता होती है, जिसके आयाम रनटाइम पर निर्धारित किए जाएंगे और आपके द्वारा डेटा संग्रहीत करने के बाद। उदाहरण के लिए, कुछ महंगी प्रक्रिया से वेक्टर आउटपुट इकट्ठा करना जहां यह गणना करना सरल नहीं है कि आपको स्टार्टअप में कितने वैक्टर की आवश्यकता होगी।
जैसे ही वे आते हैं आप अपने सभी वेक्टर इनपुट को एक बफ़र में समेट सकते हैं, लेकिन यदि आप उपयोग करते हैं तो कोड अधिक टिकाऊ और अधिक पठनीय होगा vector<vector<T>>
।