簡體   English   中英

如何在C ++ for x86中優化三個矩陣的這個產品?

[英]How to optimize this product of three matrices in C++ for x86?

我有一個密鑰算法,其中大部分運行時用於計算密集矩陣產品:

A*A'*Y, where: A is an m-by-n matrix, 
               A' is its conjugate transpose,
               Y is an m-by-k matrix

Typical characteristics:
    - k is much smaller than both m or n (k is typically < 10)
    - m in the range [500, 2000]
    - n in the range [100, 1000]

基於這些維度,根據矩陣鏈乘法問題的教訓,很明顯,在操作數意義上將計算結構化為A*(A'*Y)是最優的。 我當前的實現就是這樣做的,而只是強迫關聯性到表達式的性能提升是顯而易見的。

我的應用程序是用C ++編寫的x86_64平台。 我正在使用Eigen線性代數庫, 英特爾的數學核心庫作為后端。 Eigen能夠使用IMKL的BLAS接口來執行乘法,並且從我的Sandy Bridge機器上移動到Eigen的原生SSE2實現到Intel優化的基於AVX的實現的提升也很重要。

然而,表達式A * (A.adjoint() * Y) (以Eigen的說法編寫)被分解為兩個通用矩陣 - 矩陣乘積(調用xGEMM BLAS例程), xGEMM創建了臨時矩陣。 我想知道,通過一次專門的實現來一次評估整個表達式,我可以得到一個比我現在的通用更快的實現。 一些讓我相信這一點的觀察結果是:

  • 使用上述典型尺寸,輸入矩陣A通常不適合緩存。 因此,用於計算三矩陣乘積的特定存儲器訪問模式將是關鍵。 顯然,避免為部分產品創建臨時矩陣也是有利的。

  • A及其共軛轉置顯然具有非常相關的結構,可以利用該結構來改善整體表達的存儲器訪問模式。

是否有任何標准技術以緩存友好的方式實現這種表達式? 我發現的矩陣乘法的大多數優化技術都是針對標准的A*B情況,而不是更大的表達式。 我對這個問題的微優化方面很滿意,比如轉換成適當的SIMD指令集,但是我正在尋找任何可以用最友好的方式打破這個結構的引用。

編輯:根據到目前為止的反應,我想我上面有點不清楚。 我使用C ++ / Eigen的事實實際上只是我對這個問題的看法的一個實現細節。 Eigen在實現表達式模板方面做得很好,但是不支持將此類問題作為簡單表達式進行評估(僅支持2個通用密集矩陣的產品)。

在比編譯器如何評估表達式更高的層次上,我正在尋找復合乘法運算的更有效的數學分解,並且由於A及其共軛的共同結構而傾向於避免不必要的冗余存儲器訪問。轉。 結果可能很難在純Eigen中有效地實現,所以我可能只是在具有SIMD內在函數的專門例程中實現它。

這不是一個完整的答案(但是 - 我不確定它會變成一個)。

讓我們首先想一下數學。 由於矩陣乘法是關聯的,我們可以做(A * A') Y或A (A'* Y)。

(A * A')* Y的浮點運算

2*m*n*m + 2*m*m*k //the twos come from addition and multiplication

A *(A'* Y)的浮點運算

2*m*n*k + 2*m*n*k = 4*m*n*k

由於k遠小於m和n,因此很明顯為什么第二種情況要快得多。

但是通過對稱性我們原則上可以將A * A'的計算次數減少兩次(雖然這可能不容易用SIMD),因此我們可以減少(A * A')* Y的浮點運算次數至

m*n*m + 2*m*m*k.

我們知道m和n都大於k。 讓我們為m和n選擇一個名為z的新變量,並找出第一和第二種情況相等的情況:

z*z*z + 2*z*z*k = 4*z*z*k  //now simplify
z = 2*k.

因此,只要m和n都超過兩倍k,第二種情況就會有更少的浮點運算。 在您的情況下,m和n都大於100且k小於10,因此情況2使用的浮點運算少得多。

在高效的代碼方面。 如果代碼針對高效使用緩存進行了優化(如MKL和Eigen),那么大密集矩陣乘法是計算綁定而不是內存綁定,因此您不必擔心緩存。 MKL比Eigen更快,因為MKL使用AVX(現在可能是fma3?)。

我認為你不能比你使用第二種情況和MKL(通過Eigen)更有效地做到這一點。 啟用OpenMP以獲得最大FLOPS。

您應該通過將FLOPS與處理器的峰值FLOPS進行比較來計算效率。 假設你有一個Sandy Bridge / Ivy Bridge處理器。 峰值SP FLOPS是

frequency * number of physical cores * 8 (8-wide AVX SP) * 2 (addition + multiplication)

雙進動除以2。 如果你有Haswell並且MKL使用FMA,那么加倍峰值FLOPS。 要獲得正確的頻率,您必須對所有核心使用turbo boost值(它低於單核心)。 如果您沒有超頻系統或在Windows上使用CPU-Z或在Linux上使用Powertop,如果你有一個超頻系統,你可以查看這個。

使用臨時矩陣計算A'* Y,但要確保告訴eigen沒有混疊: temp.noalias() = A.adjoint()*Y 然后計算你的結果,再次告訴eigen對象沒有別名: result.noalias() = A*temp

只有當你執行(A*A')*Y時才會有冗余計算,因為在這種情況下(A*A')是對稱的,只需要計算的一半。 但是,正如您所注意到的那樣,執行A*(A'*Y)仍然要快得多,在這種情況下,沒有冗余計算。 我確認臨時創建的成本在這里完全可以忽略不計。

我想這會執行以下操作

result = A * (A.adjoint() * Y)

會像那樣做

temp = A.adjoint() * Y
result = A * temp;

如果您的矩陣Y適合緩存,您可以利用這樣做

result = A * (Y.adjoint() * A).adjoint()

或者,如果不允許以前的表示法,那就是這樣

temp = Y.adjoint() * A
result = A * temp.adjoint();

然后你不需要做矩陣A的伴隨,並存儲A的臨時伴隨矩陣,這將比Y的那個貴得多。

如果你的矩陣Y適合緩存,那么第一次乘法在A的列上運行循環應該快得多,然后在第二次多重復制的A行上運行(在緩存中有Y.adjoint())第一個乘法和temp.adjoint()為第二個),但我想內部特征已經在處理這些事情。

暫無
暫無

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

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