vector<vector<double>>
Yüksek performanslı bilimsel bilgi işlem kodu için bir matris sınıfı oluşturmak için (std kullanarak) kullanmak iyi bir fikir midir?
Cevabınız hayır ise. Niye ya? Teşekkürler
vector<vector<double>>
Yüksek performanslı bilimsel bilgi işlem kodu için bir matris sınıfı oluşturmak için (std kullanarak) kullanmak iyi bir fikir midir?
Cevabınız hayır ise. Niye ya? Teşekkürler
Yanıtlar:
Bu kötü bir fikir çünkü vektörün matrisinizde satır olduğu kadar uzayda çok sayıda nesne ayırması gerekiyor. Tahsisat pahalıdır, ancak öncelikle bu kötü bir fikirdir, çünkü matrisinizin verileri artık işlemci önbelleğinin kolayca erişebileceği tek bir yerde değil, belleğin etrafına dağılmış bir dizi dizide bulunmaktadır.
Aynı zamanda boşa harcanan bir depolama formatıdır: std :: vector, dizinin uzunluğu esnek olduğu için iki işaretçiyi, dizinin başlangıcına ve bir ucuna saklar. Öte yandan, bunun uygun bir matris olması için, tüm satırların uzunlukları aynı olmalıdır ve bu nedenle her bir satırın uzunluğunu bağımsız olarak saklamak yerine sadece bir kez sütun sayısını saklamak yeterli olacaktır.
std::vector
aslında üç işaretçi var: Tahsis edilen depolama bölgesinin başlangıcı, sonu ve sonu (örneğin, aramamızı sağlayan .capacity()
). Bu kapasite, boyuttan farklı olabilir, durumu daha da kötüleştirir!
Wolfgang'ın bahsettiği nedenlere ek olarak, eğer kullanırsanız vector<vector<double> >
, bir öğeyi geri almak istediğinizde iki kez serbest bırakmanız gerekir; bu, tek bir başvuru işleminden daha hesaplamalı olarak maliyetlidir. Tipik bir yaklaşım bunun yerine tek bir dizi (a vector<double>
veya a double *
) tahsis etmektir . İnsanların matris sınıflarına sözdizimi dizileri etrafını sararak, uygun indeksleri çağırmak için gereken "zihinsel ek yük" miktarını azaltmak için bazı daha sezgisel indeksleme işlemlerini sardığını gördüm.
Hayır, kullanılabilir ücretsiz doğrusal cebir kütüphanelerinden birini kullanın. Farklı kütüphaneler hakkında bir tartışma burada bulunabilir: Kullanılabilir, hızlı bir C ++ matris kütüphanesi için tavsiyeler?
Gerçekten böyle kötü bir şey mi?
@ Wolfgang: Yoğun matrisin boyutuna bağlı olarak, her satırda iki ek imleç ihmal edilebilir. Dağınık verilerle ilgili olarak, vektörlerin bitişik bellekte olduğundan emin olan özel bir tahsisatçı kullanmayı düşünebiliriz. Bellek geri dönüştürülmediği sürece, standart tahsisatçı bile, bize iki işaretçi boyutundaki bir aralıkla bitişik belleği verecektir.
@Geoff: Eğer rastgele erişim yapıyorsanız ve sadece bir dizi kullanıyorsanız, hala indeksi hesaplamanız gerekir. Daha hızlı olmayabilir.
O halde küçük bir test yapalım:
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();
}
Ve şimdi bir dizi kullanarak:
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();
}
Benim sistemimde artık net bir kazanan var (Compiler gcc 4.7 -O3 ile)
zaman vectormatrix yazdırır:
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
Ayrıca, standart tahsisatçı serbest belleği geri dönüştürmediği sürece verilerin bitişik olduğunu görüyoruz. (Tabii ki bazı serbest bırakmalardan sonra bunun garantisi yoktur.)
Zaman dizi matrisi yazdırır:
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
Bunu tavsiye etmiyorum, ancak performans sorunları nedeniyle değil. Genellikle tek bir işaretçi temizliği ve tamsayı aritmetiği kullanılarak indekslenen büyük bir sürekli veri yığını olarak tahsis edilen geleneksel bir matrise göre biraz daha az performans gösterecektir. Performans vuruşunun nedeni çoğunlukla farklılıkları önbelleğe almaktır, ancak matris boyutunuz yeterince büyüdükten sonra bu etki itfa edilir ve iç vektörler için özel bir ayırıcı kullanırsanız, önbellek sınırlarına göre ayarlanması için önbellekleme sorununu hafifletir. .
Bu, kendi başıma yapmamak için yeterli bir sebep değil, bence. Benim sebebim, çok fazla sayıda kodlama baş ağrıları yaratması. İşte bu uzun vadeli neden olur baş ağrısı listesi
HPC kitaplıklarının çoğunu kullanmak istiyorsanız, vektörünüzün üzerinde yinelemeniz ve tüm verilerini bitişik bir arabellekte yerleştirmeniz gerekir, çünkü çoğu HPC kitaplığı bu açık biçimi bekler. BLAS ve LAPACK akla geliyor, ama aynı zamanda her yerde bulunan HPC kütüphanesi MPI'yı kullanmak daha zor olurdu.
std::vector
girişleri hakkında hiçbir şey bilmiyor. Daha std::vector
fazla std::vector
s doldurursanız, hepsinin aynı boyuta sahip olduğundan emin olmak tamamen sizin işinizdir, çünkü bir matris istediğimizi ve matrislerin değişken sayıda satır (veya sütun) bulunmadığını unutmayın. Bu nedenle, dış vektörünüzün her girişi için tüm doğru yapıcıları çağırmanız gerekecektir ve kodunuzu kullanan başka std::vector<T>::push_back()
herhangi bir kişi, sonraki tüm kodların kırılmasına neden olacak olan iç vektörlerden herhangi birinde kullanma eğilimine karşı koymalıdır . Elbette sınıfınızı doğru yazarsanız buna izin veremezsiniz, ancak bunu basitçe büyük bir bitişik tahsisatla uygulamak çok daha kolaydır.
HPC programcıları sadece düşük seviye veri bekliyorlar. Onlara bir matris verirseniz, imleci matrisin ilk elemana ve bir işaretleyiciyi matrisin son elemana tutturursa, o zaman bu ikisi arasındaki tüm işaretçilerin geçerli olması ve aynı öğenin işaret etmesine dair bir beklenti vardır. matris. Bu benim ilk noktama benziyor, ancak farklı çünkü kütüphanelerle pek ilgili olmayabilir, aksine ekip üyeleri veya kodunuzu paylaştığınız herhangi biri olabilir.
İstediğiniz veri yapısının en düşük seviyeli temsiline düşmek, HPC için uzun vadede hayatınızı kolaylaştırır. Gibi perf
ve vtune
kullanımı, kodunuzun performansını artırmak için geleneksel profilleme sonuçlarıyla birleştirmeye çalışacağınız çok düşük seviye performans sayacı ölçümleri verecektir. Veri yapınız çok fazla süslü kap kullanıyorsa, önbellek özlemlerinin kap ile ilgili bir sorundan ya da algoritmanın kendisindeki verimsizliğinden kaynaklandığını anlamak zor olacaktır. Daha karmaşık kod kapları için gereklidir, ancak matris cebiri için gerçekten değiller - yalnızca s 1
std::vector
yerine verileri depolamak için n
std::vector
uğraşabilirsiniz, öyleyse devam edin.
Ben de bir kriter yazarım. Küçük boyutlu matris için (<100 * 100), performans <vektör <vektör> vektörü ve sarılmış 1 boyutlu vektör için benzerdir. Büyük boyutlu matris için (~ 1000 * 1000), 1D vektör sarılmış daha iyidir. Eigen matrisi daha kötü davranır. Eigen'in en kötüsü olması benim için şaşırtıcı.
#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;
}
Diğerlerinin de belirttiği gibi, onunla matematik yapmaya çalışmayın veya performans gösteren bir şey yapmayın.
Bununla birlikte, kodun çalışma zamanında ve verileri depolamaya başladıktan sonra boyutları belirlenecek olan 2 boyutlu bir diziyi bir araya getirmesi gerektiğinde bu yapıyı geçici olarak kullandım. Örneğin, vektör toplama, başlangıçta ne kadar vektör depolamanız gerektiğini tam olarak hesaplamanın kolay olmadığı bazı pahalı işlemlerden çıktılar.
Tüm vektör girişlerinizi girdikleri sırada tek bir tamponda birleştirebilirsiniz, ancak a kullanırsanız kod daha dayanıklı ve daha okunaklı olacaktır vector<vector<T>>
.