簡體   English   中英

使用CUDA計算積分圖像比使用CPU代碼要慢

[英]It's slower to calculate integral image using CUDA than CPU code

我正在使用CUDA實現積分圖像計算模塊,以提高性能。 但是它的速度比CPU模塊慢。 請讓我知道我做錯了。 隨后是cuda內核和主機代碼。 而且,另一個問題是...在內核SumH中,使用紋理內存比全局內存慢,imageTexture的定義如下。

texture<unsigned char, 1> imageTexture;
cudaBindTexture(0, imageTexture, pbImage);

//內核水平和垂直掃描圖像。

__global__ void SumH(unsigned char* pbImage, int* pnIntImage, __int64* pn64SqrIntImage, float rVSpan, int nWidth)
{
    int nStartY, nEndY, nIdx;
    if (!threadIdx.x)
    {
        nStartY = 1;
    }
    else
        nStartY = (int)(threadIdx.x * rVSpan);
    nEndY = (int)((threadIdx.x + 1) * rVSpan);

    for (int i = nStartY; i < nEndY; i ++)
    {
        for (int j = 1; j < nWidth; j ++)
        {
            nIdx = i * nWidth + j;
            pnIntImage[nIdx] = pnIntImage[nIdx - 1] + pbImage[nIdx - nWidth - i];
            pn64SqrIntImage[nIdx] = pn64SqrIntImage[nIdx - 1] + pbImage[nIdx - nWidth - i] * pbImage[nIdx - nWidth - i];
            //pnIntImage[nIdx] = pnIntImage[nIdx - 1] + tex1Dfetch(imageTexture, nIdx - nWidth - i);
            //pn64SqrIntImage[nIdx] = pn64SqrIntImage[nIdx - 1] + tex1Dfetch(imageTexture, nIdx - nWidth - i) * tex1Dfetch(imageTexture, nIdx - nWidth - i);
        }
    }
}
__global__ void SumV(unsigned char* pbImage, int* pnIntImage, __int64* pn64SqrIntImage, float rHSpan, int nHeight, int nWidth)
{
    int nStartX, nEndX, nIdx;
    if (!threadIdx.x)
    {
        nStartX = 1;
    }
    else
        nStartX = (int)(threadIdx.x * rHSpan);
    nEndX = (int)((threadIdx.x + 1) * rHSpan);

    for (int i = 1; i < nHeight; i ++)
    {
        for (int j = nStartX; j < nEndX; j ++)
        {
            nIdx = i * nWidth + j;
            pnIntImage[nIdx] = pnIntImage[nIdx - nWidth] + pnIntImage[nIdx];
            pn64SqrIntImage[nIdx] = pn64SqrIntImage[nIdx - nWidth] + pn64SqrIntImage[nIdx];
        }
    }
}

//主機代碼

    int nW = image_width;
    int nH = image_height;
    unsigned char* pbImage;
    int* pnIntImage;
    __int64* pn64SqrIntImage;
    cudaMallocManaged(&pbImage, nH * nW);
    // assign image gray values to pbimage
    cudaMallocManaged(&pnIntImage, sizeof(int) * (nH + 1) * (nW + 1));
    cudaMallocManaged(&pn64SqrIntImage, sizeof(__int64) * (nH + 1) * (nW + 1));
    float rHSpan, rVSpan;
        int nHThreadNum, nVThreadNum;
        if (nW + 1 <= 1024)
        {
            rHSpan = 1;
            nVThreadNum = nW + 1;
        }
        else
        {
            rHSpan = (float)(nW + 1) / 1024;
            nVThreadNum = 1024;
        }
        if (nH + 1 <= 1024)
        {
            rVSpan = 1;
            nHThreadNum = nH + 1;
        }
        else
        {
            rVSpan = (float)(nH + 1) / 1024;
            nHThreadNum = 1024;
        }

        SumH<<<1, nHThreadNum>>>(pbImage, pnIntImage, pn64SqrIntImage, rVSpan, nW + 1);
        cudaDeviceSynchronize();
        SumV<<<1, nVThreadNum>>>(pbImage, pnIntImage, pn64SqrIntImage, rHSpan, nH + 1, nW + 1);
        cudaDeviceSynchronize();

關於當前問題中的代碼。 我想提兩件事:啟動參數和計時方法。

1)啟動參數

啟動內核時,有兩個主要參數指定正在啟動的線程數量。 這些位於<<<>>>部分之間,是網格中的塊數,以及每個塊的線程數,如下所示:

foo <<< numBlocks, numThreadsPerBlock >>> (args);

為了使單個內核在當前GPU上高效運行,可以使用經驗法則,即numBlocks * numThreadsPerBlock應該至少為10,000。 IE瀏覽器。 10,000件作品。 這是一條經驗法則,因此僅使用5,000個線程即可獲得良好的結果(隨GPU的不同而不同:便宜的GPU可以使用較少的線程擺脫故障),但這是您需要至少查看的數量級。 您正在運行1024個線程。 幾乎可以肯定這還不夠(提示:內核內部的循環看起來像掃描基元,可以並行完成)。

除此之外,還需要考慮其他一些事項。

  • 與GPU上的SM數量相比,塊的數量應該更大。 開普勒K40具有15個SM,為避免明顯的尾部效應,您可能希望在此GPU上至少〜100個塊。 其他GPU的SM較少,但您尚未指定擁有的SM,因此我無法更具體地說明。
  • 每個塊的線程數不應太小。 每個SM上只能有這么多塊,因此,如果塊太小,您將無法最佳地使用GPU。 此外,在較新的GPU上, 最多四個扭曲可以同時在SM上接收指令 ,因此將塊大小設為128的倍數通常是一個好主意。

2)計時

我不會在這里深入探討,但是請確保您的時機合理。 GPU代碼傾向於具有一次性的初始化延遲。 如果這在您的時間范圍內,您將看到用於表示更大代碼的代碼的錯誤運行時。 同樣,CPU和GPU之間的數據傳輸也需要時間。 在實際的應用程序中,您只能對數千個內核調用執行一次此操作,但是在測試應用程序中,您可以在每次內核啟動時執行一次。

如果要獲得准確的計時,則必須使示例更能代表最終代碼,或者必須確保僅計時要重復的區域。

使用CUDA時,請注意以下幾點。

  1. 從主機內存復制到設備內存的速度很慢-當您將某些數據從主機復制到設備時,在將數據復制回主機之前,應進行盡可能多的計算(做所有工作)。
  2. 設備上有3種類型的內存-全局,共享,本地。 您可以按照全局<共享<本地(本地=最快)的速度對它們進行排名。
  3. 從連續的內存塊中讀取要比隨機訪問更快。 使用結構數組時,您希望將其轉置為數組結構。
  4. 您始終可以咨詢CUDA Visual Profiler,以顯示程序的瓶頸。

上面提到的GTX750具有512個CUDA內核(與着色器單元相同,只是以/ different /模式驅動)。 http://www.nvidia.de/object/geforce-gtx-750-de.html#pdpContent=2

創建積分圖像的職責只能部分並行化,因為結果數組中的任何結果值都取決於更大的前身。 此外,每次內存傳輸僅占很小的數學部分,因此ALU的功能強大,因此不可避免的內存傳輸可能成為瓶頸。 這樣的加速器可能會提供一些加速,但由於任務本身不允許這樣做,所以不能提供令人興奮的加速。

如果您要在相同的輸入數據上計算積分圖像的多個變化,則由於並行度選項和數學運算量的增加,您將更有可能看到“刺激”。 但這將是另一項職責。

作為來自Google搜索的一個瘋狂猜測-其他人已經擺弄了那些東西: https : //www.google.de/url? sa = t&rct = j&q =& esrc = s&source = web&cd =11& cad = rja&uact =8& ved = 0CD8QFjAKahUKUKEwjjnoabw8bIAhXFvhQKHb =A% 3A%2F%2Fdspace.mit.edu%2Fopenaccess-為散發%2F1721.1%2F71883&USG = AFQjCNHBbOEB_OHAzLZI9__lXO_7FPqdqA

確保的唯一方法是分析代碼,但是在這種情況下,我們可能可以做出合理的猜測。

基本上,您只是對某些數據進行一次掃描,並且對每個項目進行的處理極少。

考慮到您對每個項目進行的處理很少,使用CPU處理數據的瓶頸可能只是從內存中讀取數據。

在GPU上進行處理時,仍然需要從內存中讀取數據並將其復制到GPU的內存中。 這意味着我們仍然必須從主內存中讀取所有數據,就像CPU進行了處理一樣。 更糟糕的是,所有這些都必須寫入GPU的內存中,從而進一步降低速度。 到GPU甚至開始進行實際處理時,您已經比CPU完成工作所需的時間更多。

為了使Cuda有意義,您通常需要對每個單獨的數據項進行更多處理。 在這種情況下,CPU可能大部分時間已經幾乎處於空閑狀態,正在等待內存中的數據。 在這種情況下, 除非輸入數據已經存在於GPU的內存中, 否則 GPU不太會有幫助因此GPU可以進行處理而無需任何額外的復制。

暫無
暫無

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

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