উচ্চ পারফরম্যান্স বৈজ্ঞানিক কম্পিউটিং কোডের জন্য ম্যাট্রিক্স ক্লাস গঠনের জন্য ভেক্টর <ভেক্টর <ডাবল>> ব্যবহার করা কি ভাল ধারণা?


37

vector<vector<double>>উচ্চ পারফরম্যান্স বৈজ্ঞানিক কম্পিউটিং কোডের জন্য ম্যাট্রিক্স ক্লাস গঠনের জন্য (স্টাডি ব্যবহার করে) ব্যবহার করা কি ভাল ধারণা ?

উত্তর যদি না হয়। কেন? ধন্যবাদ


2
-1 অবশ্যই এটি একটি খারাপ ধারণা। আপনি যেমন স্টোরেজ ফর্ম্যাট সহ ব্লেস, ল্যাপ্যাক বা অন্য কোনও বিদ্যমান ম্যাট্রিক্স লাইব্রেরি ব্যবহার করতে সক্ষম হবেন না। এছাড়াও, আপনি অ অ স্থানীয়তা এবং দিকনির্দেশের দ্বারা অদক্ষতার পরিচয় দিন
টমাস ক্লিম্পেল

9
@ থমাস কি আসলেই একটি ডাউনওয়োটের ওয়ারেন্ট দেয়?
আকিদ

33
ডাউনভোট করবেন না এটি একটি বৈধ প্রশ্ন হলেও এটি যদি একটি বিভ্রান্ত ধারণা।
ওল্ফগ্যাং ব্যাঙ্গার্থ

3
std :: ভেক্টর কোনও বিতরণ করা ভেক্টর নয় তাই আপনি এর সাথে সমান্তরাল কম্পিউটিং করতে পারবেন না (ভাগ করা মেমরি মেশিন বাদে), পরিবর্তে পেটস্ক বা ট্রিলিনোস ব্যবহার করতে পারবেন। তদ্ব্যতীত একজন স্পার্স ম্যাট্রিক্সের সাথে সাধারণত ডিল করে এবং আপনি পুরো ঘন ম্যাট্রিক্স সংরক্ষণ করবেন। স্পার্স ম্যাট্রিক্সের সাথে খেলার জন্য আপনি একটি স্ট্যান্ড :: ভেক্টর <স্ট্যান্ড :: ম্যাপ> ব্যবহার করতে পারেন তবে এটি খুব ভাল পারফর্ম করতে পারে না, নীচে @ ওল্ফগ্যাংবাংয়ের পোস্টটি দেখুন।
gnzlbg

3
এমপিআই দিয়ে
স্টাড

উত্তর:


43

এটি একটি খারাপ ধারণা কারণ আপনার ম্যাট্রিক্সে সারি রয়েছে বলে ভেক্টরকে স্পেসে যতগুলি অবজেক্ট বরাদ্দ করা দরকার। বরাদ্দ ব্যয়বহুল, তবে প্রাথমিকভাবে এটি একটি খারাপ ধারণা কারণ প্রসেসর ক্যাশে সহজেই অ্যাক্সেস করতে পারে এমন এক জায়গার চেয়ে এখন আপনার ম্যাট্রিক্সের ডেটা মেমরির চারদিকে ছড়িয়ে ছিটিয়ে থাকা বিভিন্ন অ্যারেতে উপস্থিত রয়েছে।

এটি একটি অপ্রয়োজনীয় স্টোরেজ ফর্ম্যাটটিও রয়েছে: std :: ভেক্টর দুটি পয়েন্টার সঞ্চয় করে, একটি অ্যারের শুরুতে এবং শেষ প্রান্তে কারণ অ্যারের দৈর্ঘ্য নমনীয়। অন্যদিকে, এটি যথাযথ ম্যাট্রিক্স হওয়ার জন্য, সমস্ত সারিগুলির দৈর্ঘ্য সমান হতে হবে এবং তাই প্রতিটি সারির স্বাধীনভাবে তার দৈর্ঘ্য সংরক্ষণের পরিবর্তে কেবল একবারে কলামের সংখ্যা সংরক্ষণ করা যথেষ্ট।


এটি আসলে আপনার চেয়ে খারাপ, কারণ std::vectorআসলে তিনটি পয়েন্টার সঞ্চয় করে: বরাদ্দকৃত স্টোরেজ অঞ্চলের শুরু, শেষ এবং শেষ (যেমন আমাদের কল করার অনুমতি দেয়, উদাহরণস্বরূপ .capacity())। সেই ক্ষমতাটি আকারের চেয়ে আলাদা হতে পারে পরিস্থিতিকে অনেক খারাপ করে তোলে!
ব্যবহারকারী 14717

18

ওল্ফগ্যাং যে কারণগুলি উল্লেখ করেছেন, তা ছাড়াও, আপনি যদি vector<vector<double> >এটি ব্যবহার করেন, প্রতিবার যখনই কোনও উপাদান পুনরুদ্ধার করতে চান তখন আপনাকে এটি দুবার দ্বিগুণ করতে হবে, যা একটি একক ডিফারেন্সিং অপারেশনের চেয়ে গণনামূলকভাবে ব্যয়বহুল। একটি সাধারণ পদ্ধতির পরিবর্তে একটি একক অ্যারে (ক vector<double>বা ক double *) বরাদ্দ করা হয়। আমি আরও দেখেছি লোকেরা সঠিক সূচকগুলি আহ্বান করার জন্য প্রয়োজনীয় "মানসিক ওভারহেড" এর পরিমাণ হ্রাস করার জন্য এই একক অ্যারেটিকে আরও কিছু স্বজ্ঞাত ইনডেক্সিং অপারেশনগুলিতে আবদ্ধ করে ম্যাট্রিক্স ক্লাসে সিনট্যাকটিক চিনি যুক্ত করেছে।


9

না, বিনামূল্যে উপলভ্য লিনিয়ার বীজগণিত গ্রন্থাগারগুলির মধ্যে একটি ব্যবহার করুন। বিভিন্ন গ্রন্থাগার সম্পর্কে আলোচনা এখানে পাওয়া যাবে: একটি ব্যবহারযোগ্য, দ্রুত সি ++ ম্যাট্রিক্স লাইব্রেরির জন্য প্রস্তাবনাগুলি?


5

আসলেই কি এতো খারাপ জিনিস?

@ ওল্ফগ্যাং: ঘন ম্যাট্রিক্সের আকারের উপর নির্ভর করে, প্রতি সারিতে দুটি অতিরিক্ত পয়েন্টার উপেক্ষিত হতে পারে। ছড়িয়ে ছিটিয়ে থাকা ডেটা সম্পর্কিত যে কোনও কাস্টম বরাদ্দকারী ব্যবহারের কথা ভাবতে পারে যা ভেক্টরগুলি একটি স্মৃতিশক্তি রয়েছে তা নিশ্চিত করে। যতক্ষণ স্মৃতি পুনর্ব্যবহৃত হয় না ততক্ষণ স্ট্যান্ডার্ড বরাদ্দকারী দুটি পয়েন্টার আকারের ফাঁক দিয়ে আমাদের স্মৃতি মেমরি করবে 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

আপনি লিখুন "আমার সিস্টেমে এখন পরিষ্কার বিজয়ী" - আপনার অর্থ কি কোনও পরিষ্কার বিজয়ী নেই?
আকিদ

9
-1 এইচপিসি কোডের পারফরম্যান্স বোঝা অনর্থক হতে পারে। আপনার ক্ষেত্রে, ম্যাট্রিক্সের আকার কেবল ক্যাশের আকার ছাড়িয়ে যায়, যাতে আপনি কেবল আপনার সিস্টেমের মেমরির ব্যান্ডউইদথ মাপছেন। যদি আমি এনকে 200 তে পরিবর্তন করি এবং পুনরাবৃত্তির সংখ্যা 1000 করে বাড়িয়ে তুলি তবে আমি "ক্যালক নেওয়া: 65" বনাম "গণনা: 36" পেয়েছি। যদি আমি আরও আরও বাস্তবসম্মত করতে a = a * a দ্বারা ++ a1 * a2 দ্বারা প্রতিস্থাপন করি তবে আমি "ক্যালক নেওয়া: 176" বনাম "গণনা: 84" পেয়েছি। সুতরাং দেখে মনে হচ্ছে আপনি ম্যাট্রিক্সের পরিবর্তে ভেক্টরের ভেক্টর ব্যবহার করে পারফরম্যান্সে একটি ফ্যাক্টর টু আলগা করতে পারেন। বাস্তব জীবন আরও জটিল হবে, তবে এটি এখনও একটি খারাপ ধারণা।
টমাস ক্লিম্পেল

হ্যাঁ তবে এমপিআই দিয়ে
স্টাড

4

আমি এটি প্রস্তাব দিচ্ছি না, তবে পারফরম্যান্স সমস্যার কারণে নয়। এটি একটি traditionalতিহ্যবাহী ম্যাট্রিক্সের তুলনায় কিছুটা কম পারফরম্যান্ট হবে, যা সাধারণত একক পয়েন্টার ডিरेফারেন্স এবং পূর্ণসংখ্যার গাণিতিক ব্যবহার করে সূচকযুক্ত সংলগ্ন ডেটাগুলির একটি বড় অংশ হিসাবে বরাদ্দ করা হয়। পারফরম্যান্স হিট হওয়ার কারণটি হ'ল বেশিরভাগ ক্ষেত্রে পার্থক্য রয়েছে, তবে আপনার ম্যাট্রিক্সের আকারটি যথেষ্ট পরিমাণে বেড়ে গেলে এই প্রভাবটি প্রশমিত হয়ে যাবে এবং আপনি যদি অভ্যন্তরীণ ভেক্টরগুলির জন্য একটি বিশেষ বরাদ্দকারী ব্যবহার করেন যাতে তারা ক্যাশে সীমানায় বিন্যস্ত হয় তবে এটি আরও ক্যাচিং সমস্যাটি প্রশমিত করে দেয় ।

এটি নিজেই এটি করার পক্ষে যথেষ্ট কারণ নয়, আমার মতে। আমার কারণ হ'ল এটি প্রচুর কোডিং মাথা ব্যাথা তৈরি করে। মাথাব্যথার একটি তালিকা এখানে দীর্ঘমেয়াদী হতে পারে

এইচপিসি গ্রন্থাগার ব্যবহার

আপনি যদি বেশিরভাগ এইচপিসি লাইব্রেরি ব্যবহার করতে চান তবে আপনাকে আপনার ভেক্টরের উপরে পুনরাবৃত্তি করতে হবে এবং তাদের সমস্ত ডেটা একটি সামঞ্জস্যপূর্ণ বাফারে রাখতে হবে, কারণ বেশিরভাগ এইচপিসি লাইব্রেরি এই স্পষ্ট ফর্ম্যাটটি আশা করে। BLAS এবং LAPACK মাথায় আসে, তবে সর্বব্যাপী এইচপিসি লাইব্রেরি MPI ব্যবহার করা আরও কঠিন হবে।

কোডিং ত্রুটির আরও সম্ভাবনা

std::vectorএর এন্ট্রি সম্পর্কে কিছুই জানে না। আপনি যদি একটি ভরাট তাহলে std::vectorআরো অনেক কিছু দিয়ে std::vectorগুলি তারপর এটি সম্পূর্ণরূপে আপনার কাজ কারণ মনে রাখবেন যে আমরা একটি ম্যাট্রিক্স চান এবং ম্যাট্রিক্স সারি (বা কলাম) পরিবর্তনশীল নম্বর না নিশ্চিত তারা সব একই আকার আছে করতে, না। সুতরাং আপনাকে আপনার বাইরের ভেক্টরের প্রতিটি প্রবেশের জন্য সমস্ত সঠিক কনস্ট্রাক্টরকে কল করতে হবে এবং আপনার কোড ব্যবহার করা অন্য যে কোনও ব্যক্তিকে অবশ্যই std::vector<T>::push_back()অভ্যন্তরীণ ভেক্টরের কোনওটিতে ব্যবহার করার প্রলোভনের প্রতিরোধ করতে হবে, যার ফলে নিম্নলিখিত সমস্ত কোডটি ভেঙে যেতে পারে। অবশ্যই আপনি এটিকে অস্বীকার করতে পারেন যদি আপনি নিজের ক্লাসটি সঠিকভাবে লিখে থাকেন তবে এটি কেবল একটি বৃহত অনিয়মিত বরাদ্দ দিয়ে কার্যকর করা অনেক সহজ।

এইচপিসি সংস্কৃতি এবং প্রত্যাশা

এইচপিসি প্রোগ্রামাররা কেবল নিম্ন স্তরের ডেটা আশা করে। আপনি যদি তাদের একটি ম্যাট্রিক্স দেন তবে এমন একটি প্রত্যাশা রয়েছে যে তারা যদি ম্যাট্রিক্সের প্রথম উপাদানটির জন্য পয়েন্টার এবং ম্যাট্রিক্সের শেষ উপাদানটির একটি পয়েন্টার ধরে থাকে তবে এই দুটিয়ের মধ্যে থাকা সমস্ত পয়েন্টার বৈধ এবং একইটির উপাদানগুলির দিকে নির্দেশ করে ম্যাট্রিক্স। এটি আমার প্রথম পয়েন্টের মতো, তবে ভিন্ন কারণ এটি গ্রন্থাগারগুলির সাথে এতটা সম্পর্কিত নাও হতে পারে বরং দলের সদস্য বা আপনি যার সাথে আপনার কোড ভাগ করে নেন।

নিম্ন স্তরের ডেটার কর্মক্ষমতা সম্পর্কে যুক্তিযুক্ত করা সহজ Eas

আপনার কাঙ্ক্ষিত ডেটা কাঠামোর সর্বনিম্ন স্তরের প্রতিনিধিত্বকে ফেলে দেওয়া এইচপিসির জন্য দীর্ঘকালীন আপনার জীবনকে সহজ করে তোলে। মত সরঞ্জাম ব্যবহার perfএবং vtuneআপনি খুব নিম্ন স্তরের পারফরম্যান্স কাউন্টার পরিমাপ যা আপনি আপনার কোড কর্মক্ষমতা উন্নত করার ঐতিহ্যগত প্রোফাইলিং ফলাফল সঙ্গে মেশা করতে চেষ্টা করবে দেব। যদি আপনার ডেটা স্ট্রাকচারটি প্রচুর অভিনব পাত্রে ব্যবহার করে তবে এটি বুঝতে অসুবিধা হবে যে ক্যাশে মিসগুলি পাত্রে কোনও সমস্যা থেকে আসে বা নিজেই অ্যালগরিদমের কোনও অদক্ষতা। আরও জটিল কোড ধারকগুলির জন্য প্রয়োজনীয়, তবে ম্যাট্রিক্স বীজগণিতের জন্য সেগুলি সত্যই নয় - আপনি কেবল এস এর 1 std::vectorপরিবর্তে ডেটা সংরক্ষণ করার মাধ্যমে পেতে পারেন n std::vector, তাই এটি দিয়ে যান।


1

আমি একটি বেঞ্চমার্কও লিখি। ছোট আকারের ম্যাট্রিক্সের জন্য (<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;
}

0

অন্যরা যেমন উল্লেখ করেছে, এটির সাথে গণিত করার চেষ্টা করবেন না বা কোনও পারফরম্যান্ট করবেন না।

এটি বলেছিল, কোডটি একটি 2-D অ্যারে একত্রিত করার দরকার হয় যখন রানটাইম এবং আপনার ডেটা সংরক্ষণ করা শুরু করার পরে যখন তার মাত্রা নির্ধারণ করা হবে তখন আমি এই কাঠামোটিকে অস্থায়ী হিসাবে ব্যবহার করেছি। উদাহরণস্বরূপ, কিছু ব্যয়বহুল প্রক্রিয়া থেকে ভেক্টর আউটপুট সংগ্রহ করা যেখানে আপনার প্রারম্ভের সময় ঠিক কতগুলি ভেক্টর সংরক্ষণ করতে হবে তা গণনা করা সহজ নয়।

আপনার সমস্ত ভেক্টর ইনপুটগুলি কেবল একটি বাফারে প্রবেশ করার সাথে সাথে আপনি তাদের সাথে সংযুক্ত করতে পারেন, তবে আপনি যদি একটি ব্যবহার করেন তবে কোডটি আরও টেকসই এবং আরও পঠনযোগ্য হবে vector<vector<T>>

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.