《Mali OpenCL SDK v1.1.0》教程樣例之三“內存緩衝區”


  本文講述如何在Mali-T600系列GPU和CPU之間高效共享內存。


介紹


  當處理大量數據時(在OpenCL應用中這是典型的情況),確保主機與OpenCL設備之間儘可能高效地共享內存是非常重要的。我們已在hello world樣例中看到了如何使用內存緩衝區。hello world例程遵循了我們認爲的對於在主機和OpenCL設備之間共享內存的“最佳實踐”。這一教程講述這些最佳實踐方法。除非另作說明,否則所有代碼片段來自"hello_world_vector.cpp"。


共享內存系統


  典型地,OpenCL運行在應用處理器和GPU具有獨立內存的系統上。爲了在這些系統中共享CPU與GPU之間的內存,你必須分配緩衝區,拷貝數據從/來自獨立內存。

  典型地,帶Mali GPU的系統具有共享內存,因此你無需拷貝數據。然而,OpenCL假設內存是分開的,緩衝區分配涉及內存拷貝。這相當浪費,因爲拷貝需要時間並且消耗能量。


跨設備數據共享


  爲了避免拷貝,使用OpenCL API來分配內存緩衝區,並使用映射操作。這些操作同時使能了應用處理器和Mali GPU來訪問數據而無需任何拷貝。注意:在分配內存緩衝區之前,必須先初始化OpenCL。


1. 分配內存緩衝區


  應用程序通過調用clCreateBuffer(),來創建可以傳遞數據從/到內核的緩衝區對象。所有的內存都分配在一個共享的內存系統上,該共享的內存系統在物理上可以同時被CPU和GPU核訪問。然而,只有由clCreateBuffer()分配的內存,纔會被同時映射到CPU和GPU的虛擬內存空間。因此,由malloc()等函數分配的內存,只會被映射到CPU上(如圖1)。


圖1:由用戶(malloc)分配的內存不會被映射到GPU內存空間


  因此,使用CL_MEM_USE_HOST_PTR標誌調用clCreateBuffer(),傳遞到用戶創建的緩衝區,需要創建一個新的緩衝區並拷貝數據(同CL_MEM_COPY_HOST_PTR)。這個拷貝使得性能下降(如圖2)。


圖2:clCreateBuffer(CL_MEM_USE_HOST_PTR)創建一個新的緩衝區,然後複製數據(拷貝操作是代價高昂的)


  所以,總是儘可能地使用CL_MEM_ALLOC_HOST_PTR標誌。這樣就分配了一塊CPU和GPU都可以訪問的內存,無需拷貝(如圖3)。


圖3:clCreateBuffer(CL_MEM_ALLOC_HOST_PTR)創建了一個同時對CPU和GPU可見的緩衝區


    memoryObjects[0] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR, bufferSize, NULL, &errorNumber);
    createMemoryObjectsSuccess &= checkSuccess(errorNumber);
    memoryObjects[1] = clCreateBuffer(context, CL_MEM_READ_ONLY | CL_MEM_ALLOC_HOST_PTR, bufferSize, NULL, &errorNumber);
    createMemoryObjectsSuccess &= checkSuccess(errorNumber);
    memoryObjects[2] = clCreateBuffer(context, CL_MEM_WRITE_ONLY | CL_MEM_ALLOC_HOST_PTR, bufferSize, NULL, &errorNumber);
    createMemoryObjectsSuccess &= checkSuccess(errorNumber);


2. 映射內存對象


  如前面所述,我們映射由OpenCL實現創建的內存緩衝區到指針,從而可以在CPU端訪問它們。

    cl_int* inputA = (cl_int*)clEnqueueMapBuffer(commandQueue, memoryObjects[0], CL_TRUE, CL_MAP_WRITE, 0, bufferSize, 0, NULL, NULL, &errorNumber);
    mapMemoryObjectsSuccess &= checkSuccess(errorNumber);
    cl_int* inputB = (cl_int*)clEnqueueMapBuffer(commandQueue, memoryObjects[1], CL_TRUE, CL_MAP_WRITE, 0, bufferSize, 0, NULL, NULL, &errorNumber);
    mapMemoryObjectsSuccess &= checkSuccess(errorNumber);

3. 使用C指針初始化數據


  一旦緩衝區被映射到一個cl_int類型的指針,它們可以像普通的C指針那樣來使用,以初始化數據。

    for (int i = 0; i < arraySize; i++)
    {
       inputA[i] = i;
       inputB[i] = i;
    }

4. 取消內存對象的映射


  當我們結束使用內存對象,從CPU端對它們取消映射。之所以這麼做是因爲:

    (1). 在OpenCL端的內核內部,讀寫那個內存的行爲是未定義的;

    (2). 在完成時,OpenCL實現無法釋放那個內存。

    if (!checkSuccess(clEnqueueUnmapMemObject(commandQueue, memoryObjects[0], inputA, 0, NULL, NULL)))
    {
       cleanUpOpenCL(context, commandQueue, program, kernel, memoryObjects, numberOfMemoryObjects);
       cerr << "Unmapping memory objects failed " << __FILE__ << ":"<< __LINE__ << endl;
       return 1;
    }
    if (!checkSuccess(clEnqueueUnmapMemObject(commandQueue, memoryObjects[1], inputB, 0, NULL, NULL)))
    {
       cleanUpOpenCL(context, commandQueue, program, kernel, memoryObjects, numberOfMemoryObjects);
       cerr << "Unmapping memory objects failed " << __FILE__ << ":"<< __LINE__ << endl;
       return 1;
    }


補充建議


  除了上面的建議,我們附加下述建議:

    (1).請勿使用私有(private)或局部(local)內存,使用這些在Mali-T600系列GPU上並無性能增益。

    (2). 如果你的內核是在有限內存帶寬的情況下,使用一個公式來計算變量,而不是從內存中讀取。












發佈了33 篇原創文章 · 獲贊 8 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章