簡體   English   中英

C中的快速2D卷積

[英]Fast 2D Convolution in C

我正在嘗試用Python實現卷積神經網絡。 最初,我使用scipy.signal的convolve2d函數來進行卷積,但它有很多開銷,在C中實現我自己的算法並從python中調用它會更快,因為我知道我的輸入是什么樣的。

我實現了2個功能:

  1. 使用不可分離的內核卷積矩陣
  2. 用可分離的內核對矩陣進行卷積(現在我假設python在將它傳遞給C之前進行等級檢查和拆分)

由於我需要降低維數,因此這兩個函數都沒有填充。

不可分離的2D卷積

// a - 2D matrix (as a 1D array), w - kernel
double* conv2(double* a, double* w, double* result)
{
    register double acc;
    register int i; 
    register int j;
    register int k1, k2;
    register int l1, l2;
    register int t1, t2;

    for(i = 0; i < RESULT_DIM; i++) 
    {
        t1 = i * RESULT_DIM; // loop invariants
        for(j = 0; j < RESULT_DIM; j++) 
        {   
            acc = 0.0;
            for(k1 = FILTER_DIM - 1, k2 = 0; k1 >= 0; k1--, k2++)
            {
                t2 = k1 * FILTER_DIM;  // loop invariants
                for(l1 = FILTER_DIM - 1, l2 = 0; l1 >= 0; l1--, l2++)
                {
                    acc += w[t2 + l1] * a[(i + k2) * IMG_DIM + (j + l2)];
                }
            }
            result[t1 + j] = acc;
        }
    }

    return result;
}

可分離的2D卷積

// a - 2D matrix, w1, w2 - the separated 1D kernels
double* conv2sep(double* a, double* w1, double* w2, double* result)
{
    register double acc;
    register int i; 
    register int j;
    register int k1, k2;
    register int t;
    double* tmp = (double*)malloc(IMG_DIM * RESULT_DIM * sizeof(double));

    for(i = 0; i < RESULT_DIM; i++) // convolve with w1 
    {
        t = i * RESULT_DIM;
        for(j = 0; j < IMG_DIM; j++)
        {
            acc = 0.0;
            for(k1 = FILTER_DIM - 1, k2 = 0; k1 >= 0; k1--, k2++)
            {
                acc += w1[k1] * a[k2 * IMG_DIM + t + j];
            }
            tmp[t + j] = acc;
        }
    }

    for(i = 0; i < RESULT_DIM; i++) // convolve with w2
    {
        t = i * RESULT_DIM;
        for(j = 0; j < RESULT_DIM; j++)
        {
            acc = 0.0;
            for(k1 = FILTER_DIM - 1, k2 = 0; k1 >= 0; k1--, k2++)
            {
                acc += w2[k1] * tmp[t + (j + k2)];
            }

            result[t + j] = acc;
        }
    }

    free(tmp);
    return result;
}

使用gcc的-O3標志進行編譯並在2.7GHz Intel i7上進行測試,使用4000x4000矩陣和5x5內核,我分別得到(平均5):

271.21900 ms
127.32000 ms

與scipy.signal的convolve2d相比,這仍然是一個相當大的改進,對於相同的操作需要大約2秒鍾,但我需要更多速度,因為我將調用此函數數千次。 將數據類型更改為浮動目前不是一種選擇,即使它會導致相當大的加速。

有沒有辦法可以進一步優化這些算法? 我可以應用任何緩存技巧或例程來加快速度嗎?

任何建議,將不勝感激。

如果您只在x86上運行,請考慮使用SSE或AVX SIMD優化。 對於double數據,吞吐量的改善將是適度的,但如果你可以切換到float那么你可以使用SSE或使用AVX獲得8倍的改進。 關於StackOverflow上的這個主題,有很多問題和答案,您可以從中獲得有關實現的一些想法。 另外,還有許多可用的庫,包括高性能2D卷積(過濾)例程,這些通常利用SIMD來提高性能,例如Intel的IPP(商業)或OpenCV(免費)。

另一種可能性是利用多個核心 - 將圖像分割成塊並在其自己的線程中運行每個塊。 例如,如果您有一個4核CPU,則將圖像分成4個塊。 (見pthreads )。

如果您真的想要完全優化此操作,您當然可以結合上述兩個想法。


您可以應用於當前代碼以及任何未來實現(例如SIMD)的一些小優化:

  • 如果您的內核是對稱的(或奇數對稱的),那么您可以通過添加(減去)對稱輸入值並執行一次乘法而不是兩次來減少操作次數

  • 對於可分離的情況,不要分配一個完整的幀臨時緩沖區,而是考慮使用“條帶挖掘”方法 - 分配一個較小的緩沖區,這是一個全寬,但行數相對較少,然后以“條帶”處理你的圖像,交替應用水平內核和垂直內核。 這樣做的好處是,您具有更多緩存友好的訪問模式和更小的內存占用。


關於編碼風格的一些評論:

  • register關鍵字多年來一直是多余的,如果你試圖使用它,現代編譯器會發出警告 - 通過拋棄它來節省一些噪音(和一些打字)

  • 在C中轉換malloc的結果是不受歡迎的 - 這是多余的並且有潛在危險

  • 使任何輸入參數const (即只讀)並對任何永遠不會混淆的參數使用restrict (例如aresult ) - 這不僅有助於避免編程錯誤(至少在const的情況下),而是在某些情況下,它可以幫助編譯器生成更好的優化代碼(特別是在潛在的別名指針的情況下)。

暫無
暫無

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

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