1

Мне нужно найти информацию о том, как Unified Shader Array получает доступ к памяти GPU, чтобы иметь представление о том, как эффективно использовать ее. Образ архитектуры моей видеокарты не показывает это четко.Доступ к памяти GPU в OpenCL/C++ Amp

enter image description here

нужно загрузить большое изображение в память GPU с помощью C++ Amp и разделить его на мелкие кусочки (например, 4x4 пикселей). Каждый кусок должен вычисляться с помощью другого потока. Я не знаю, как потоки разделяют доступ к изображению.

enter image description here

Есть ли способ сделать это таким образом, что потоки не блокируют друг друга при обращении изображение? Может быть, у них есть собственная память, к которой можно получить доступ исключительно?

enter image description here

Или, может быть доступ к единой памяти настолько быстро, что я не должен заботиться об этом (но я не верю в это)? Это действительно важно, потому что мне нужно вычислить около 10 тыс. Подмножеств для каждого изображения.

ответ

1

Для C++ AMP вы хотите загрузить данные, которые каждая нить в пределах плитки использует в памяти tile_static, прежде чем начинать расчет свертки. Поскольку каждый поток обращается к пикселям, которые также читаются другими потоками, это позволяет вам делать одно чтение для каждого пикселя из (медленной) глобальной памяти и кэшировать его в (быстрой) статической памяти с черепицей, чтобы все последующие чтения были быстрее.

Вы можете увидеть example of tiling for convolution here. Метод DetectEdgeTiled загружает все требуемые данные и вызовы idx.barrier.wait(), чтобы гарантировать, что все потоки завершили запись данных в статическую память плитки. Затем он выполняет код обнаружения края, используя память tile_static. В образцах есть много других примеров этого шаблона. Обратите внимание, что код загрузки в DetectEdgeTiled является сложным только потому, что он должен учитывать дополнительные пиксели вокруг края пикселей, которые записываются в текущем фрагменте, и по существу является развернутым циклом, следовательно, это длина.

Я не уверен, что вы думаете о проблеме совершенно правильным способом. Здесь есть два уровня разделения. Чтобы вычислить новое значение для каждого пикселя, поток, выполняющий эту работу, считывает блок окружающих пикселей.Кроме того, блоки (плитки) потоков загружают более крупные блоки пиксельных данных в память tile_static. Каждый поток на плитке затем вычисляет результат для одного пикселя внутри блока.

void ApplyEdgeDetectionTiledHelper(const array<ArgbPackedPixel, 2>& srcFrame, 
            array<ArgbPackedPixel, 2>& destFrame) 
{  
    tiled_extent<tileSize, tileSize> computeDomain = GetTiledExtent(srcFrame.extent); 
    parallel_for_each(computeDomain.tile<tileSize, tileSize>(), [=, &srcFrame, &destFrame, &orgFrame](tiled_index<tileSize, tileSize> idx) restrict(amp) 
    { 
     DetectEdgeTiled(idx, srcFrame, destFrame, orgFrame); 
    }); 
} 

void DetectEdgeTiled(
    tiled_index<tileSize, tileSize> idx, 
    const array<ArgbPackedPixel, 2>& srcFrame, 
    array<ArgbPackedPixel, 2>& destFrame) restrict(amp) 
{ 
    const UINT shift = imageBorderWidth/2; 
    const UINT startHeight = 0; 
    const UINT startWidth = 0; 
    const UINT endHeight = srcFrame.extent[0];  
    const UINT endWidth = srcFrame.extent[1]; 

    tile_static RgbPixel localSrc[tileSize + imageBorderWidth ] 
     [tileSize + imageBorderWidth]; 

    const UINT global_idxY = idx.global[0]; 
    const UINT global_idxX = idx.global[1]; 
    const UINT local_idxY = idx.local[0]; 
    const UINT local_idxX = idx.local[1]; 

    const UINT local_idx_tsY = local_idxY + shift; 
    const UINT local_idx_tsX = local_idxX + shift; 

    // Copy image data to tile_static memory. The if clauses are required to deal with threads that own a 
    // pixel close to the edge of the tile and need to copy additional halo data. 

    // This pixel 
    index<2> gNew = index<2>(global_idxY, global_idxX); 
    localSrc[local_idx_tsY][local_idx_tsX] = UnpackPixel(srcFrame[gNew]); 

    // Left edge 
    if (local_idxX < shift) 
    { 
     index<2> gNew = index<2>(global_idxY, global_idxX - shift); 
     localSrc[local_idx_tsY][local_idx_tsX-shift] = UnpackPixel(srcFrame[gNew]); 
    } 
    // Right edge 
    // Top edge 
    // Bottom edge 
    // Top Left corner 
    // Bottom Left corner 
    // Bottom Right corner 
    // Top Right corner 

    // Synchronize all threads so that none of them start calculation before 
    // all data is copied onto the current tile. 

    idx.barrier.wait(); 

    // Make sure that the thread is not referring to a border pixel 
    // for which the filter cannot be applied. 
    if ((global_idxY >= startHeight + 1 && global_idxY <= endHeight - 1) && 
     (global_idxX >= startWidth + 1 && global_idxX <= endWidth - 1)) 
    { 
     RgbPixel result = Convolution(localSrc, index<2>(local_idx_tsY, local_idx_tsX)); 
     destFrame[index<2>(global_idxY, global_idxX)] = result; 
    } 
} 

Этот код был снят с CodePlex, и я убрал много реальной реализации, чтобы сделать его более ясным.

Ответ WRT @ sharpneli вы можете использовать texture<> в C++ AMP для достижения того же результата, что и изображения OpenCL. Также есть пример этого на CodePlex.

+0

Но при использовании OpenCL 'texture <>' как бы я разбил фрагменты/блоки так, чтобы они также кэшировали части изображения? Что касается вашего примера, я не совсем понимаю, как работают сдвиги и смещения. –

+1

Здесь есть еще один пример, если это поможет http://blogs.msdn.com/b/nativeconcurrency/archive/2011/11/01/convolution-sample.aspx –

0

В этом конкретном случае вам не о чем беспокоиться. Просто используйте OpenCL-изображения. Графические процессоры чрезвычайно хороши для простого чтения изображений (из-за текстурирования). Однако этот метод требует записи результата в отдельное изображение, потому что вы не можете читать и писать с одного и того же изображения в одном ядре. Вы должны использовать это, если вы можете выполнить вычисление в виде одного прохода (не нужно итерации).

Другой способ - получить доступ к нему как к обычному буферу памяти, загрузить части в волновом фронте (группа потоков, выполняющихся в синхронизации) в локальную память (эта память невероятно быстро), выполнить вычисление и записать полный конечный результат обратно в после вычисления. Вы должны использовать этот подход, если вам нужно читать и писать значения на одном и том же изображении во время вычислений. Если вы не связаны с памятью, вы все равно можете прочитать исходные значения из текстуры, а затем итерировать в локальной памяти и записать конечные результаты в отдельное изображение.

Считывание из унифицированной памяти происходит медленно, только если это не const * ограничение, а несколько потоков - одно и то же. В общем случае, если последующий идентификатор потока считывает последующие местоположения, это довольно быстро. Однако, если ваши потоки записывают и читают в унифицированную память, тогда это будет медленным.

+0

Я хотел бы сделать это как можно быстрее. Я хочу написать выводное изображение, которое отличается от исходного изображения. Насколько я понимаю, вы предлагаете мне хранить все подмножества в разных местах. Я не совсем понимаю, что вы делаете ** загружая части в волновой фронт (группа потоков, выполняемых в синхронизации) в локальную память **. Не могли бы вы рассказать об этом? Может быть, изображение, представляющее это решение? –

+0

Эта страница дает хороший пример в виде 2d-свертки. http://www.cmsoft.com.br/index.php?option=com_content&view=category&layout=blog&id=142&Itemid=201. В нем также есть несколько изображений, объясняющих концепцию. В основном каждая группа потоков просто имеет кучу доступной памяти, которая чрезвычайно быстра по сравнению с глобальной памятью. Таким образом, он используется в качестве кэша, управляемого пользователем. – sharpneli

Смежные вопросы