Yüksek performanslı bilimsel bilgi işlem kodu için bir matris sınıfı oluşturmak üzere <vector <double>> vektörünü kullanmak iyi bir fikir midir?


36

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


2
-1 Elbette bu kötü bir fikir. Blas, lapack ya da mevcut herhangi bir matris kütüphanesini böyle bir depolama formatıyla kullanamazsınız. Ek olarak, yerel olmayan ve dolaysız verilere göre verimsizlikleri de ortaya koyuyorsunuz
Thomas Klimpel

9
@Thomas Bu gerçekten bir aşağı oylama emri veriyor mu?
akid

33
Oy kullanma Yanlış yönlendirilmiş bir fikir olsa bile meşru bir soru.
Wolfgang Bangerth

3
std :: vector dağıtılmış bir vektör değildir, onunla çok fazla paralel hesaplama yapamazsınız (paylaşılan bellek makineleri hariç), bunun yerine Petsc veya Trilinos kullanın. Ayrıca, bir kişi genellikle seyrek matrislerle uğraşır ve siz de yoğun matrisleri depolarsınız. Seyrek matrislerle oynamak için bir std :: vector <std :: map> kullanabilirsiniz, ancak yine de bu çok iyi sonuç vermedi, aşağıdaki @WolfgangBangerth yazısına bakınız.
gnzlbg

3
kullanmayı deneyin std :: MPI ile ve kendini ateş isteyeceksiniz >> vektör <std :: vector <double
pyCthon

Yanıtlar:


42

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.


Aslında söylediğinizden daha kötü, çünkü std::vectoraslı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!
user14717,

18

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.



4

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

Sen "kesin kazanan orada şimdi benim sistemde" yazma - bunu mu demek istediniz hiçbir kesin kazanan?
akid

9
-1 hpc kodunun performansını anlamak önemsiz olabilir. Sizin durumunuzda, matrisin boyutu sadece önbellek boyutunu aştığı için sisteminizin hafıza bant genişliğini ölçüyorsunuz. Eğer N'yi 200'e değiştirip yineleme sayısını 1000'e yükseltirsem, "calc took: 65" vs "calc took: 36" alırım. Daha gerçekçi hale getirmek için a = a * a'yı a = a1 * a2 ile değiştirirsem, "calc took: 176" vs "calc took: 84" alırım. Böylece, bir matris yerine bir vektör vektörünü kullanarak, bir performans iki faktörünü kaybedebilirsiniz. Gerçek hayat daha karmaşık olacak, ama yine de kötü bir fikir.
Thomas Klimpel

evet ama MPI ile std :: vektörler kullanmayı deneyin, C eller aşağı kazanır
pyCthon

3

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 kütüphanelerinin kullanımı

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.

Kodlama hatası için daha fazla potansiyel

std::vectorgirişleri hakkında hiçbir şey bilmiyor. Daha std::vectorfazla std::vectors 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 kültürü ve beklentileri

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.

Düşük seviyeli veri performansı hakkında aklınıza daha kolay

İ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 perfve vtunekullanı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::vectoryerine verileri depolamak için n std::vectoruğraşabilirsiniz, öyleyse devam edin.


0

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;
}

0

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>>.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.