[英]Memory layout : 2D N*M data as pointer to N*M buffer or as array of N pointers to arrays
我正在猶豫如何組織2D數據的內存布局。 基本上,我想要的是N * M 2D double
精度數組,其中N〜M數以千計(並且是從用戶提供的數據派生的)
我看到的方式有2種選擇:
double *data = new double[N*M];
要么
double **data = new double*[N];
for (size_t i = 0; i < N; ++i)
data[i] = new double[M];
首選是我所追求的。 我看到的主要優點是:較短的新/刪除語法,連續的內存布局(如果我正確安排訪問權限的話)意味着在運行時對相鄰內存的訪問,以及矢量化代碼(自動矢量化或使用矢量庫,例如vDSP或vecLib)的性能可能更高
另一方面,與分配一堆較小的連續內存相比,在我看來分配大塊連續內存可能會失敗/花費更多時間。 第二種方法的優勢還在於,與data[i*M+j]
相比,語法data[i][j]
較短
什么是最常見/更好的方法,主要是如果我嘗試從性能的角度來看它(即使這些改進很小,但我很想知道哪種性能更好)。
在前兩個選擇之間,對於M
和N
合理值,我幾乎可以肯定會選擇第一個選擇。跳過指針取消引用,如果以正確的順序訪問數據,則可以得到很好的緩存。
考慮到您對尺寸的擔心,我們可以做一些后台計算。
由於M
和N
為數千,因此假定每個上限為10000
。 那么您消耗的總內存為
10000 * 10000 * sizeof(double) = 8 * 10^8
這大約是800 MB,雖然很大,但考慮到現代機器中的內存大小,這是相當合理的。
如果N
和M
是常量,最好將所需的內存靜態聲明為二維數組。 或者,您可以使用std::array
。
std::array<std::array<double, M>, N> data;
如果只有M
是一個常數,則可以使用std::array
的std::vector
代替。
std::vector<std::array<double, M>> data(N);
如果M
不是常數,則需要執行一些動態分配。 但是, std::vector
可用於為您管理該內存,因此您可以圍繞它創建一個簡單的包裝器。 下面的包裝返回一個row
中間對象,以允許second []
運算符實際將偏移量計算到vector
。
template <typename T>
class matrix {
const size_t N;
const size_t M;
std::vector<T> v_;
struct row {
matrix &m_;
const size_t r_;
row (matrix &m, size_t r) : m_(m), r_(r) {}
T & operator [] (size_t c) { return m_.v_[r_ * m_.M + c]; }
T operator [] (size_t c) const { return m_.v_[r_ * m_.M + c]; }
};
public:
matrix (size_t n, size_t m) : N(n), M(m), v_(N*M) {}
row operator [] (size_t r) { return row(*this, r); }
const row & operator [] (size_t r) const { return row(*this, r); }
};
matrix<double> data(10,20);
data[1][2] = .5;
std::cout << data[1][2] << '\n';
解決您對性能的特別關注時:希望進行單個內存訪問的理由是正確的。 但是,您應該避免執行new
並delete
自己(這是該包裝程序提供的功能),並且如果將數據更自然地解釋為多維,那么在代碼中顯示出來也將使代碼也更易於閱讀。
第二種方法所示的多重分配較差,因為它會花費更多時間,但是其優點是,如果您的系統碎片化,則分配可能會更成功(空閑內存由較小的孔組成,並且您沒有空閑的內存塊)足以滿足單個分配請求的內存)。 但是多重分配還有另一個缺點,那就是需要更多的內存來為指向每一行的指針分配空間。
我的建議是提供單一分配技術,而無需顯式調用new
和delete
,因為內存是由vector
管理的。 同時,它允許使用二維語法[x][y]
尋址數據。 因此,只要您有足夠的內存來滿足分配請求,它就可以提供單一分配的所有好處以及多重分配的所有好處。
考慮使用類似以下的內容:
// array of pointers to doubles to point the beginning of rows
double ** data = new double*[N];
// allocate so many doubles to the first row, that it is long enough to feed them all
data[0] = new double[N * M];
// distribute pointers to individual rows as well
for (size_t i = 1; i < N; i++)
data[i] = data[0] + i * M;
我不確定這是否是通用做法,我只是想出了這一點。 這種方法仍然存在一些缺點,但是我認為它消除了大多數方法,例如能夠訪問data[i][j]
等所有雙精度數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.