簡體   English   中英

最有效的方法來計算矩陣的每個元素的指數

[英]Most efficient way to calculate the exponential of each element of a matrix

我正在從Matlab遷移到C + GSL,我想知道什么是計算矩陣B的最有效方法:

B[i][j] = exp(A[i][j])

其中i在[0,Ny]中,j在[0,Nx]中。

請注意,這與矩陣指數不同:

B = exp(A)

這可以通過GSL(linalg.h)中的一些不穩定/不支持的代碼來完成。

我剛剛找到了強力解決方案(幾個'for'循環),但有沒有更明智的方法呢?

編輯

來自Drew Hall的解決方案的結果

所有結果都來自1024x1024 for(for)循環,其中在每次迭代中分配兩個double值(復數)。 時間是超過100次執行的平均時間

  • 考慮{Row,Column} - 存儲矩陣的主要模式時的結果:
    • 在Row-Major模式下循環內部循環中的行時為226.56 ms(情況1)。
    • 在Row-Major模式下循環內循環中的列時為223.22 ms(情況2)。
    • 使用GSL提供的gsl_matrix_complex_set函數時的224.60 ms(案例3)。

案例1的源代碼

for(i=0; i<Nx; i++)
{
    for(j=0; j<Ny; j++)
    {
        /* Operations to obtain c_value (including exponentiation) */
        matrix[2*(i*s_tda + j)] = GSL_REAL(c_value);
        matrix[2*(i*s_tda + j)+1] = GSL_IMAG(c_value);
    }
}

案例2的源代碼

for(i=0; i<Nx; i++)
{
    for(j=0; j<Ny; j++)
    {
        /* Operations to obtain c_value (including exponentiation) */
        matrix->data[2*(j*s_tda + i)] = GSL_REAL(c_value);
        matrix->data[2*(j*s_tda + i)+1] = GSL_IMAG(c_value);
    }
}

案例3的源代碼

for(i=0; i<Nx; i++)
{
    for(j=0; j<Ny; j++)
    {
        /* Operations to obtain c_value (including exponentiation) */
        gsl_matrix_complex_set(matrix, i, j, c_value);
    }
}

沒有辦法避免遍歷所有元素並在每個元素上調用exp()或等效元素。 但是有更快更慢的迭代方式。

特別是,您的目標應該是最大限度地減少緩存未命中。 找出你的數據是以行主要順序還是按列主順序存儲,並確保循環使得內部循環迭代在內存中連續存儲的元素,並且外部循環將大步向下一行( if row major)或column(如果是major major)。 雖然這看起來微不足道,但它可以在性能上產生巨大的差異(取決於矩陣的大小)。

處理完緩存后,您的下一個目標是消除循環開銷。 第一步(如果您的矩陣API支持它)是從嵌套循環(M&N邊界)到迭代基礎數據(M N界限)的單個循環 你需要得到一個指向底層內存塊的原始指針(即雙倍而不是雙倍**)才能做到這一點。

最后,拋出一些循環展開(也就是說,為循環的每次迭代做8或16個元素)以進一步減少循環開銷,這可能就像你可以做到的那樣快。 你可能需要一個帶有fall-through的最終switch語句來清理其余的元素(當你的數組大小為%block size!= 0時)。

不,除非有一些我沒有聽說過的奇怪的數學怪癖,你幾乎只需要用兩個for循環遍歷元素。

如果您只想將exp應用於數組,那么實際上沒有捷徑。 你得打電話給它(Nx * Ny)次。 如果某些矩陣元素很簡單,比如0,或者有重復的元素,那么一些memoization可能會有所幫助。

但是,如果您真正想要的是矩陣指數(這是非常有用的),我們依賴的算法是DGPADM 它在Fortran中,但您可以使用f2c將其轉換為C. 這是關於它的文章。

由於未顯示循環的內容,計算c_value的位我們不知道代碼的性能是受內存帶寬限制還是受CPU限制。 確切知道的唯一方法是使用分析器,並使用復雜的分析器。 它需要能夠測量內存延遲,即CPU等待數據從RAM到達的空閑時間。

如果您受內存帶寬的限制,那么一旦您按順序訪問內存,就無法做很多事情。 順序獲取數據時,CPU和內存最有效。 隨機訪問達到了吞吐量,因為數據更有可能必須從RAM中提取到緩存中。 你總是可以嘗試獲得更快的RAM。

如果您受到CPU的限制,那么您可以使用更多選項。 使用SIMD是一種選擇,手動編碼浮點代碼(由於許多原因,C / C ++編譯器在FPU代碼上不是很好)。 如果這是我,並且內循環中的代碼允許它,我將有兩個指向數組的指針,一個在開始時,另一個在第4個/ 5個中。 在每次迭代中,將使用第一指針和使用第二指針的標量FPU操作來執行SIMD操作,使得循環的每次迭代執行五個值。 然后,我將SIMD指令與FPU指令交錯,以降低延遲成本。 這不應該影響您的緩存,因為(至少在Pentium上)MMU可以同時流式傳輸多達四個數據流(即為您預取數據而無需任何提示或特殊指令)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM