Apakah ide yang baik untuk menggunakan vektor <vektor <double>> untuk membentuk kelas matriks untuk kode komputasi ilmiah kinerja tinggi?
37
Apakah ide yang baik untuk menggunakan vector<vector<double>>(menggunakan std) untuk membentuk kelas matriks untuk kode komputasi ilmiah kinerja tinggi?
-1 Tentu saja itu ide yang buruk. Anda tidak akan dapat menggunakan blas, lapack atau perpustakaan matriks lain yang ada dengan format penyimpanan seperti itu. Selain itu, Anda memperkenalkan inefisiensi dengan data non-lokalitas dan tipuan
Thomas Klimpel
9
@ Thomas Apakah itu benar-benar menjamin downvote?
akid
33
Jangan downvote. Ini adalah pertanyaan yang sah bahkan jika itu adalah ide yang salah arah.
Wolfgang Bangerth
3
std :: vector bukanlah vektor terdistribusi sehingga Anda tidak akan dapat melakukan banyak komputasi paralel dengannya (kecuali untuk mesin memori bersama), gunakan Petsc atau Trilinos sebagai gantinya. Lebih jauh lagi, biasanya satu berurusan dengan matriks jarang dan Anda akan menyimpan Matriks padat penuh. Untuk bermain dengan matriks jarang, Anda dapat menggunakan std :: vector <std :: map> tetapi sekali lagi, ini tidak berkinerja sangat baik, lihat posting @WolfgangBangerth di bawah ini.
gnzlbg
3
coba gunakan std :: vector <std :: vector <double>> dengan MPI dan Anda ingin memotret diri Anda sendiri
pyCthon
Jawaban:
43
Itu ide yang buruk karena vektor perlu mengalokasikan objek sebanyak mungkin di ruang karena ada baris dalam matriks Anda. Alokasi mahal, tetapi terutama itu adalah ide yang buruk karena data matriks Anda sekarang ada di sejumlah array yang tersebar di sekitar memori, daripada semua di satu tempat di mana cache prosesor dapat dengan mudah mengaksesnya.
Ini juga merupakan format penyimpanan yang boros: std :: vector menyimpan dua pointer, satu ke awal array dan satu lagi ke akhir karena panjang array fleksibel. Di sisi lain, agar ini menjadi matriks yang tepat, panjang semua baris harus sama sehingga cukup untuk menyimpan jumlah kolom hanya sekali, daripada membiarkan setiap baris menyimpan panjangnya secara independen.
Ini sebenarnya lebih buruk daripada yang Anda katakan, karena std::vectorsebenarnya menyimpan tiga petunjuk: Awal, akhir, dan akhir wilayah penyimpanan yang dialokasikan (memungkinkan kami untuk menelepon, misalnya, .capacity()). Kapasitas itu bisa berbeda dari ukuran membuat situasinya jauh lebih buruk!
user14717
18
Selain alasan yang disebutkan Wolfgang, jika Anda menggunakan a vector<vector<double> >, Anda harus melakukan dereferensi dua kali setiap kali Anda ingin mengambil elemen, yang secara komputasi lebih mahal daripada operasi dereferencing tunggal. Salah satu pendekatan yang umum adalah mengalokasikan satu array (a vector<double>atau a double *) sebagai gantinya. Saya juga melihat orang menambahkan gula sintaksis ke kelas matriks dengan membungkus array tunggal ini beberapa operasi pengindeksan yang lebih intuitif, untuk mengurangi jumlah "overhead mental" yang diperlukan untuk memohon indeks yang tepat.
@ Wolfgang: Tergantung pada ukuran matriks padat, dua pointer tambahan per baris mungkin dapat diabaikan. Mengenai data yang tersebar, seseorang dapat berpikir untuk menggunakan pengalokasi kustom yang memastikan bahwa vektor berada dalam memori yang berdekatan. Selama memori tidak didaur ulang, bahkan pengalokasi standar akan menggunakan memori yang berdekatan dengan celah ukuran dua penunjuk.
@ Geoff: Jika Anda melakukan akses acak dan menggunakan hanya satu array Anda masih harus menghitung indeks. Mungkin tidak akan lebih cepat.
Di sistem saya sekarang ada pemenang yang jelas (Compiler gcc 4.7 dengan -O3)
cetakan waktu vectormatrix:
index 997:3
index 998:3
index 999:30xc7fc680xc7fc80
calc took:185.507 k=100000000
real 0m0.257s
user 0m0.244s
sys 0m0.008s
Kita juga melihat, bahwa selama pengalokasi standar tidak mendaur ulang memori yang dibebaskan, data tersebut bersebelahan. (Tentu saja setelah beberapa deallocations tidak ada jaminan untuk ini.)
cetakan arraymatrix:
index 997:1
index 998:1
index 999:10x7ff41f208f480x7ff41f208f50
calc took:187.349 k=100000000
real 0m0.257s
user 0m0.248s
sys 0m0.004s
Anda menulis "Di sistem saya sekarang ada pemenang yang jelas" - apakah maksud Anda tidak ada pemenang yang jelas?
akid
9
-1 Memahami kinerja kode hpc bisa nontrivial. Dalam kasus Anda, ukuran matriks hanya melebihi ukuran cache, sehingga Anda hanya mengukur bandwidth memori sistem Anda. Jika saya mengubah N menjadi 200 dan menambah jumlah iterasi ke 1000, saya mendapatkan "calc take: 65" vs "calc take: 36". Jika saya lebih lanjut mengganti a = a * dengan a + = a1 * a2 untuk membuatnya lebih realistis, saya mendapatkan "calc take: 176" vs "calc took: 84". Jadi sepertinya Anda bisa kehilangan faktor dua dalam kinerja dengan menggunakan vektor vektor, bukan matriks. Kehidupan nyata akan lebih rumit, tetapi itu masih ide yang buruk.
Thomas Klimpel
yeah tetapi coba gunakan std :: vektor dengan MPI, C menang
telak
4
Saya tidak merekomendasikannya, tetapi bukan karena masalah kinerja. Ini akan menjadi sedikit kurang berkinerja daripada matriks tradisional, yang biasanya dialokasikan sebagai bagian besar dari data yang berdekatan yang diindeks menggunakan dereference pointer tunggal dan aritmatika integer. Alasan untuk hit kinerja sebagian besar perbedaan caching, tetapi setelah ukuran matriks Anda cukup besar efek ini akan diamortisasi dan jika Anda menggunakan pengalokasi khusus untuk vektor bagian dalam sehingga mereka selaras dengan batas-batas cache maka ini lebih lanjut mengurangi masalah caching .
Dengan sendirinya itu bukan alasan yang cukup untuk tidak melakukannya, menurut saya. Alasan saya adalah membuat banyak sakit kepala kode. Berikut daftar sakit kepala yang akan ditimbulkan dalam jangka panjang
Penggunaan perpustakaan HPC
Jika Anda ingin menggunakan sebagian besar pustaka HPC Anda harus mengulangi vektor Anda dan menempatkan semua datanya dalam buffer yang bersebelahan, karena sebagian besar pustaka HPC mengharapkan format eksplisit ini. BLAS dan LAPACK datang ke pikiran, tetapi juga MPI perpustakaan HPC mana-mana akan jauh lebih sulit untuk digunakan.
Lebih banyak potensi kesalahan pengkodean
std::vectortidak tahu apa-apa tentang entri mereka. Jika Anda mengisi std::vectordengan lebih dari std::vectors maka itu sepenuhnya tugas Anda untuk memastikan bahwa mereka semua memiliki ukuran yang sama, karena ingat bahwa kami ingin matriks dan matriks tidak memiliki jumlah baris (atau kolom) yang bervariasi. Dengan demikian Anda harus memanggil semua konstruktor yang benar untuk setiap entri vektor luar Anda, dan siapa pun yang menggunakan kode Anda harus menahan godaan untuk digunakan std::vector<T>::push_back()pada salah satu vektor bagian dalam, yang akan menyebabkan semua kode berikut rusak. Tentu saja Anda dapat melarang ini jika Anda menulis kelas dengan benar, tetapi jauh lebih mudah untuk menegakkan ini hanya dengan alokasi bersebelahan besar.
Budaya dan harapan HPC
Programer HPC hanya mengharapkan data level rendah. Jika Anda memberi mereka sebuah matriks, ada harapan bahwa jika mereka meraih pointer ke elemen pertama dari matriks dan sebuah pointer ke elemen terakhir dari matriks, maka semua pointer di antara keduanya valid dan arahkan ke elemen yang sama. matriks. Ini mirip dengan poin pertama saya, tetapi berbeda karena mungkin tidak terkait banyak dengan perpustakaan tetapi anggota tim atau siapa pun yang Anda bagikan kode Anda.
Lebih mudah untuk alasan tentang kinerja data tingkat yang lebih rendah
Menurunkan ke tingkat representasi terendah dari struktur data yang Anda inginkan menjadikan hidup Anda lebih mudah dalam jangka panjang untuk HPC. Menggunakan alat-alat seperti perfdan vtuneakan memberi Anda pengukuran penghitung kinerja tingkat sangat rendah yang akan Anda coba gabungkan dengan hasil profil tradisional untuk meningkatkan kinerja kode Anda. Jika struktur data Anda menggunakan banyak wadah mewah, akan sulit untuk memahami bahwa kesalahan cache berasal dari masalah dengan wadah atau ketidakefisienan dalam algoritma itu sendiri. Diperlukan wadah kode yang lebih rumit, tetapi untuk aljabar matriks sebenarnya tidak - Anda bisa bertahan hanya 1std::vectordengan menyimpan data daripada nstd::vectors, jadi ikuti saja.
Saya juga menulis patokan. Untuk matriks ukuran kecil (<100 * 100), kinerjanya mirip untuk vektor <vektor <ganda >> dan membungkus vektor 1D. Untuk matriks ukuran besar (~ 1000 * 1000), vektor 1D yang dibungkus lebih baik. Matriks Eigen berperilaku lebih buruk. Sangat mengejutkan bagi saya bahwa Eigen adalah yang terburuk.
Seperti yang telah ditunjukkan orang lain, jangan mencoba melakukan matematika dengan itu atau melakukan pemain apa pun.
Yang mengatakan, saya telah menggunakan struktur ini sebagai sementara ketika kode perlu merakit array 2-D yang dimensinya akan ditentukan pada saat runtime dan setelah Anda mulai menyimpan data. Misalnya, mengumpulkan keluaran vektor dari beberapa proses mahal di mana tidak mudah untuk menghitung dengan tepat berapa banyak vektor yang perlu Anda simpan saat startup.
Anda bisa menggabungkan semua input vektor Anda menjadi satu buffer saat mereka masuk, tetapi kode akan lebih tahan lama dan lebih mudah dibaca jika Anda menggunakan a vector<vector<T>>.
Jawaban:
Itu ide yang buruk karena vektor perlu mengalokasikan objek sebanyak mungkin di ruang karena ada baris dalam matriks Anda. Alokasi mahal, tetapi terutama itu adalah ide yang buruk karena data matriks Anda sekarang ada di sejumlah array yang tersebar di sekitar memori, daripada semua di satu tempat di mana cache prosesor dapat dengan mudah mengaksesnya.
Ini juga merupakan format penyimpanan yang boros: std :: vector menyimpan dua pointer, satu ke awal array dan satu lagi ke akhir karena panjang array fleksibel. Di sisi lain, agar ini menjadi matriks yang tepat, panjang semua baris harus sama sehingga cukup untuk menyimpan jumlah kolom hanya sekali, daripada membiarkan setiap baris menyimpan panjangnya secara independen.
sumber
std::vector
sebenarnya menyimpan tiga petunjuk: Awal, akhir, dan akhir wilayah penyimpanan yang dialokasikan (memungkinkan kami untuk menelepon, misalnya,.capacity()
). Kapasitas itu bisa berbeda dari ukuran membuat situasinya jauh lebih buruk!Selain alasan yang disebutkan Wolfgang, jika Anda menggunakan a
vector<vector<double> >
, Anda harus melakukan dereferensi dua kali setiap kali Anda ingin mengambil elemen, yang secara komputasi lebih mahal daripada operasi dereferencing tunggal. Salah satu pendekatan yang umum adalah mengalokasikan satu array (avector<double>
atau adouble *
) sebagai gantinya. Saya juga melihat orang menambahkan gula sintaksis ke kelas matriks dengan membungkus array tunggal ini beberapa operasi pengindeksan yang lebih intuitif, untuk mengurangi jumlah "overhead mental" yang diperlukan untuk memohon indeks yang tepat.sumber
Tidak, gunakan salah satu perpustakaan aljabar linear gratis yang tersedia. Diskusi tentang berbagai pustaka dapat ditemukan di sini: Rekomendasi untuk pustaka matriks C ++ yang dapat digunakan dan cepat?
sumber
Apakah ini benar-benar hal yang buruk?
@ Wolfgang: Tergantung pada ukuran matriks padat, dua pointer tambahan per baris mungkin dapat diabaikan. Mengenai data yang tersebar, seseorang dapat berpikir untuk menggunakan pengalokasi kustom yang memastikan bahwa vektor berada dalam memori yang berdekatan. Selama memori tidak didaur ulang, bahkan pengalokasi standar akan menggunakan memori yang berdekatan dengan celah ukuran dua penunjuk.
@ Geoff: Jika Anda melakukan akses acak dan menggunakan hanya satu array Anda masih harus menghitung indeks. Mungkin tidak akan lebih cepat.
Jadi mari kita lakukan tes kecil:
vectormatrix.cc:
Dan sekarang menggunakan satu array:
arraymatrix.cc
Di sistem saya sekarang ada pemenang yang jelas (Compiler gcc 4.7 dengan -O3)
cetakan waktu vectormatrix:
Kita juga melihat, bahwa selama pengalokasi standar tidak mendaur ulang memori yang dibebaskan, data tersebut bersebelahan. (Tentu saja setelah beberapa deallocations tidak ada jaminan untuk ini.)
cetakan arraymatrix:
sumber
Saya tidak merekomendasikannya, tetapi bukan karena masalah kinerja. Ini akan menjadi sedikit kurang berkinerja daripada matriks tradisional, yang biasanya dialokasikan sebagai bagian besar dari data yang berdekatan yang diindeks menggunakan dereference pointer tunggal dan aritmatika integer. Alasan untuk hit kinerja sebagian besar perbedaan caching, tetapi setelah ukuran matriks Anda cukup besar efek ini akan diamortisasi dan jika Anda menggunakan pengalokasi khusus untuk vektor bagian dalam sehingga mereka selaras dengan batas-batas cache maka ini lebih lanjut mengurangi masalah caching .
Dengan sendirinya itu bukan alasan yang cukup untuk tidak melakukannya, menurut saya. Alasan saya adalah membuat banyak sakit kepala kode. Berikut daftar sakit kepala yang akan ditimbulkan dalam jangka panjang
Penggunaan perpustakaan HPC
Jika Anda ingin menggunakan sebagian besar pustaka HPC Anda harus mengulangi vektor Anda dan menempatkan semua datanya dalam buffer yang bersebelahan, karena sebagian besar pustaka HPC mengharapkan format eksplisit ini. BLAS dan LAPACK datang ke pikiran, tetapi juga MPI perpustakaan HPC mana-mana akan jauh lebih sulit untuk digunakan.
Lebih banyak potensi kesalahan pengkodean
std::vector
tidak tahu apa-apa tentang entri mereka. Jika Anda mengisistd::vector
dengan lebih daristd::vector
s maka itu sepenuhnya tugas Anda untuk memastikan bahwa mereka semua memiliki ukuran yang sama, karena ingat bahwa kami ingin matriks dan matriks tidak memiliki jumlah baris (atau kolom) yang bervariasi. Dengan demikian Anda harus memanggil semua konstruktor yang benar untuk setiap entri vektor luar Anda, dan siapa pun yang menggunakan kode Anda harus menahan godaan untuk digunakanstd::vector<T>::push_back()
pada salah satu vektor bagian dalam, yang akan menyebabkan semua kode berikut rusak. Tentu saja Anda dapat melarang ini jika Anda menulis kelas dengan benar, tetapi jauh lebih mudah untuk menegakkan ini hanya dengan alokasi bersebelahan besar.Budaya dan harapan HPC
Programer HPC hanya mengharapkan data level rendah. Jika Anda memberi mereka sebuah matriks, ada harapan bahwa jika mereka meraih pointer ke elemen pertama dari matriks dan sebuah pointer ke elemen terakhir dari matriks, maka semua pointer di antara keduanya valid dan arahkan ke elemen yang sama. matriks. Ini mirip dengan poin pertama saya, tetapi berbeda karena mungkin tidak terkait banyak dengan perpustakaan tetapi anggota tim atau siapa pun yang Anda bagikan kode Anda.
Lebih mudah untuk alasan tentang kinerja data tingkat yang lebih rendah
Menurunkan ke tingkat representasi terendah dari struktur data yang Anda inginkan menjadikan hidup Anda lebih mudah dalam jangka panjang untuk HPC. Menggunakan alat-alat seperti
perf
danvtune
akan memberi Anda pengukuran penghitung kinerja tingkat sangat rendah yang akan Anda coba gabungkan dengan hasil profil tradisional untuk meningkatkan kinerja kode Anda. Jika struktur data Anda menggunakan banyak wadah mewah, akan sulit untuk memahami bahwa kesalahan cache berasal dari masalah dengan wadah atau ketidakefisienan dalam algoritma itu sendiri. Diperlukan wadah kode yang lebih rumit, tetapi untuk aljabar matriks sebenarnya tidak - Anda bisa bertahan hanya1
std::vector
dengan menyimpan data daripadan
std::vector
s, jadi ikuti saja.sumber
Saya juga menulis patokan. Untuk matriks ukuran kecil (<100 * 100), kinerjanya mirip untuk vektor <vektor <ganda >> dan membungkus vektor 1D. Untuk matriks ukuran besar (~ 1000 * 1000), vektor 1D yang dibungkus lebih baik. Matriks Eigen berperilaku lebih buruk. Sangat mengejutkan bagi saya bahwa Eigen adalah yang terburuk.
sumber
Seperti yang telah ditunjukkan orang lain, jangan mencoba melakukan matematika dengan itu atau melakukan pemain apa pun.
Yang mengatakan, saya telah menggunakan struktur ini sebagai sementara ketika kode perlu merakit array 2-D yang dimensinya akan ditentukan pada saat runtime dan setelah Anda mulai menyimpan data. Misalnya, mengumpulkan keluaran vektor dari beberapa proses mahal di mana tidak mudah untuk menghitung dengan tepat berapa banyak vektor yang perlu Anda simpan saat startup.
Anda bisa menggabungkan semua input vektor Anda menjadi satu buffer saat mereka masuk, tetapi kode akan lebih tahan lama dan lebih mudah dibaca jika Anda menggunakan a
vector<vector<T>>
.sumber