vector<vector<double>>
উচ্চ পারফরম্যান্স বৈজ্ঞানিক কম্পিউটিং কোডের জন্য ম্যাট্রিক্স ক্লাস গঠনের জন্য (স্টাডি ব্যবহার করে) ব্যবহার করা কি ভাল ধারণা ?
উত্তর যদি না হয়। কেন? ধন্যবাদ
vector<vector<double>>
উচ্চ পারফরম্যান্স বৈজ্ঞানিক কম্পিউটিং কোডের জন্য ম্যাট্রিক্স ক্লাস গঠনের জন্য (স্টাডি ব্যবহার করে) ব্যবহার করা কি ভাল ধারণা ?
উত্তর যদি না হয়। কেন? ধন্যবাদ
উত্তর:
এটি একটি খারাপ ধারণা কারণ আপনার ম্যাট্রিক্সে সারি রয়েছে বলে ভেক্টরকে স্পেসে যতগুলি অবজেক্ট বরাদ্দ করা দরকার। বরাদ্দ ব্যয়বহুল, তবে প্রাথমিকভাবে এটি একটি খারাপ ধারণা কারণ প্রসেসর ক্যাশে সহজেই অ্যাক্সেস করতে পারে এমন এক জায়গার চেয়ে এখন আপনার ম্যাট্রিক্সের ডেটা মেমরির চারদিকে ছড়িয়ে ছিটিয়ে থাকা বিভিন্ন অ্যারেতে উপস্থিত রয়েছে।
এটি একটি অপ্রয়োজনীয় স্টোরেজ ফর্ম্যাটটিও রয়েছে: std :: ভেক্টর দুটি পয়েন্টার সঞ্চয় করে, একটি অ্যারের শুরুতে এবং শেষ প্রান্তে কারণ অ্যারের দৈর্ঘ্য নমনীয়। অন্যদিকে, এটি যথাযথ ম্যাট্রিক্স হওয়ার জন্য, সমস্ত সারিগুলির দৈর্ঘ্য সমান হতে হবে এবং তাই প্রতিটি সারির স্বাধীনভাবে তার দৈর্ঘ্য সংরক্ষণের পরিবর্তে কেবল একবারে কলামের সংখ্যা সংরক্ষণ করা যথেষ্ট।
std::vector
আসলে তিনটি পয়েন্টার সঞ্চয় করে: বরাদ্দকৃত স্টোরেজ অঞ্চলের শুরু, শেষ এবং শেষ (যেমন আমাদের কল করার অনুমতি দেয়, উদাহরণস্বরূপ .capacity()
)। সেই ক্ষমতাটি আকারের চেয়ে আলাদা হতে পারে পরিস্থিতিকে অনেক খারাপ করে তোলে!
ওল্ফগ্যাং যে কারণগুলি উল্লেখ করেছেন, তা ছাড়াও, আপনি যদি vector<vector<double> >
এটি ব্যবহার করেন, প্রতিবার যখনই কোনও উপাদান পুনরুদ্ধার করতে চান তখন আপনাকে এটি দুবার দ্বিগুণ করতে হবে, যা একটি একক ডিফারেন্সিং অপারেশনের চেয়ে গণনামূলকভাবে ব্যয়বহুল। একটি সাধারণ পদ্ধতির পরিবর্তে একটি একক অ্যারে (ক vector<double>
বা ক double *
) বরাদ্দ করা হয়। আমি আরও দেখেছি লোকেরা সঠিক সূচকগুলি আহ্বান করার জন্য প্রয়োজনীয় "মানসিক ওভারহেড" এর পরিমাণ হ্রাস করার জন্য এই একক অ্যারেটিকে আরও কিছু স্বজ্ঞাত ইনডেক্সিং অপারেশনগুলিতে আবদ্ধ করে ম্যাট্রিক্স ক্লাসে সিনট্যাকটিক চিনি যুক্ত করেছে।
না, বিনামূল্যে উপলভ্য লিনিয়ার বীজগণিত গ্রন্থাগারগুলির মধ্যে একটি ব্যবহার করুন। বিভিন্ন গ্রন্থাগার সম্পর্কে আলোচনা এখানে পাওয়া যাবে: একটি ব্যবহারযোগ্য, দ্রুত সি ++ ম্যাট্রিক্স লাইব্রেরির জন্য প্রস্তাবনাগুলি?
আসলেই কি এতো খারাপ জিনিস?
@ ওল্ফগ্যাং: ঘন ম্যাট্রিক্সের আকারের উপর নির্ভর করে, প্রতি সারিতে দুটি অতিরিক্ত পয়েন্টার উপেক্ষিত হতে পারে। ছড়িয়ে ছিটিয়ে থাকা ডেটা সম্পর্কিত যে কোনও কাস্টম বরাদ্দকারী ব্যবহারের কথা ভাবতে পারে যা ভেক্টরগুলি একটি স্মৃতিশক্তি রয়েছে তা নিশ্চিত করে। যতক্ষণ স্মৃতি পুনর্ব্যবহৃত হয় না ততক্ষণ স্ট্যান্ডার্ড বরাদ্দকারী দুটি পয়েন্টার আকারের ফাঁক দিয়ে আমাদের স্মৃতি মেমরি করবে will
@ জিওফ: আপনি যদি এলোমেলোভাবে অ্যাক্সেস করতে থাকেন এবং মাত্র একটি অ্যারে ব্যবহার করেন তবে আপনাকে সূচক গণনা করতে হবে। দ্রুত হতে পারে না।
সুতরাং একটি ছোট পরীক্ষা করা যাক:
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();
}
আমার সিস্টেমে এখন স্পষ্ট বিজয়ী রয়েছে (কমপ্লায়ার জিসিসি 4.7 -O3 সহ)
সময় ভেক্টরমাট্রিক্স প্রিন্ট:
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
আমি এটি প্রস্তাব দিচ্ছি না, তবে পারফরম্যান্স সমস্যার কারণে নয়। এটি একটি traditionalতিহ্যবাহী ম্যাট্রিক্সের তুলনায় কিছুটা কম পারফরম্যান্ট হবে, যা সাধারণত একক পয়েন্টার ডিरेফারেন্স এবং পূর্ণসংখ্যার গাণিতিক ব্যবহার করে সূচকযুক্ত সংলগ্ন ডেটাগুলির একটি বড় অংশ হিসাবে বরাদ্দ করা হয়। পারফরম্যান্স হিট হওয়ার কারণটি হ'ল বেশিরভাগ ক্ষেত্রে পার্থক্য রয়েছে, তবে আপনার ম্যাট্রিক্সের আকারটি যথেষ্ট পরিমাণে বেড়ে গেলে এই প্রভাবটি প্রশমিত হয়ে যাবে এবং আপনি যদি অভ্যন্তরীণ ভেক্টরগুলির জন্য একটি বিশেষ বরাদ্দকারী ব্যবহার করেন যাতে তারা ক্যাশে সীমানায় বিন্যস্ত হয় তবে এটি আরও ক্যাচিং সমস্যাটি প্রশমিত করে দেয় ।
এটি নিজেই এটি করার পক্ষে যথেষ্ট কারণ নয়, আমার মতে। আমার কারণ হ'ল এটি প্রচুর কোডিং মাথা ব্যাথা তৈরি করে। মাথাব্যথার একটি তালিকা এখানে দীর্ঘমেয়াদী হতে পারে
আপনি যদি বেশিরভাগ এইচপিসি লাইব্রেরি ব্যবহার করতে চান তবে আপনাকে আপনার ভেক্টরের উপরে পুনরাবৃত্তি করতে হবে এবং তাদের সমস্ত ডেটা একটি সামঞ্জস্যপূর্ণ বাফারে রাখতে হবে, কারণ বেশিরভাগ এইচপিসি লাইব্রেরি এই স্পষ্ট ফর্ম্যাটটি আশা করে। BLAS এবং LAPACK মাথায় আসে, তবে সর্বব্যাপী এইচপিসি লাইব্রেরি 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), মোড়ানো 1 ডি ভেক্টর ভাল। ইগেন ম্যাট্রিক্স আরও খারাপ আচরণ করে। আমার কাছে অবাক লাগে যে ইগেন সবচেয়ে খারাপ।
#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-D অ্যারে একত্রিত করার দরকার হয় যখন রানটাইম এবং আপনার ডেটা সংরক্ষণ করা শুরু করার পরে যখন তার মাত্রা নির্ধারণ করা হবে তখন আমি এই কাঠামোটিকে অস্থায়ী হিসাবে ব্যবহার করেছি। উদাহরণস্বরূপ, কিছু ব্যয়বহুল প্রক্রিয়া থেকে ভেক্টর আউটপুট সংগ্রহ করা যেখানে আপনার প্রারম্ভের সময় ঠিক কতগুলি ভেক্টর সংরক্ষণ করতে হবে তা গণনা করা সহজ নয়।
আপনার সমস্ত ভেক্টর ইনপুটগুলি কেবল একটি বাফারে প্রবেশ করার সাথে সাথে আপনি তাদের সাথে সংযুক্ত করতে পারেন, তবে আপনি যদি একটি ব্যবহার করেন তবে কোডটি আরও টেকসই এবং আরও পঠনযোগ্য হবে vector<vector<T>>
।