20 julho, 2013 0 Comentários AUTOR: elemarjr CATEGORIAS: Sem categoria Tags:,

Fundamentos de C++ AMP - Parte 3 - array e array_view

Tempo de leitura: Menos de um minuto

Olá. Tudo certo?

Esse é o terceiro post da série onde apresento os fundamentos de C++ AMP - a extensão da linguagem C++ e biblioteca, da Microsoft, para desenvolvimento de programas com paralelização massiva, usando a GPU.

Os posts anteriores foram:

  1. O que é? Para que serve? Hello World!
  2. accelerator e accelerator_view

Nesse post falo sobre outras duas classes fundamentais: array e array_view

A classe array

A classe array é definida através de um template disponível no namespace concurrency. Esse template solicita dois parâmetros: 1) o tipo que será colecionado e 2) o número de dimensões que será utilizada.

A classe array representa uma coleção de informações, todas do mesmo tipo, localizadas na memória de um accelerator, geralmente GPU.

Abaixo, um exemplo breve para criação de um array do C++ AMP

array<int, 1> anArray(6);

Como você deve imaginar, esse array representa uma coleção, unidimensional, de inteiros com seis elementos. Abaixo, algumas outras inicializações:

array<float, 2> aMatrix(4, 4);
array<int, 3> aCube(3, 3, 3);

Entendido? Agora, um exemplo completo que indica o padrão de uso:

#include <iostream>
#include <vector>
#include <amp.h>

using namespace concurrency;
using namespace std;

int main() {

	float seed = 0;
	vector<float> v(10);
	generate(v.begin(), v.end(), [&seed](){return seed++;});

	array<float> a(10);
	copy(v.begin(), a);
	parallel_for_each(a.extent, [&](index<1> idx) restrict (amp) {
		a[idx]+=2;
	});
	copy(a, v.begin());

	for_each(v.cbegin(), v.cend(), [](int element){
		wcout << element << endl;
	});
	return 1;
}
&#91;/code&#93;

O que fizemos? 1) copiamos os dados de um vetor convencional para um array (levamos os dados da memória da CPU para a GPU); 2) realizamos algum processamento da GPU (somamos 2 para cada elemento) e 3) copiamos os valores do array para o vector (levamos os dados da memória da GPU para a CPU).

Vale observar que a "cópia" dos dados do vetor poderia ser abreviada no construtor do objeto matrix:

&#91;code language="cpp"&#93;
array<float> a(10, v.begin());

A função concurrency::copy oferece diversas sobrecargas para mover dados de diversos containers para um array e vice-versa. O intellisense do Visual Studio é uma ótima maneira de explorar todas as possibilidades.

A classe array_view

Um array, como vimos, representa dados em um accelerator. Podemos construir um array e preenche-lo com dados em apenas uma linha de código como acabamos de ver. Geralmente, será necessário mover seus dados de volta para um container convencional posteriormente, para utilização ótima na CPU.

Podemos escrever programas inteiros com C++ AMP usando apenas arrays. Entretanto há outra opção: array_view. Um array_view parece com um array para o accelerator, mas nos poupa da necessidade de levar e trazer dados da CPU para a GPU.

Para ilustrar o uso de um array_view, segue o mesmo programa que escrevemos antes, agora fazendo uso desta classe:

#include
#include
#include

using namespace concurrency;
using namespace std;

int main() {

float seed = 0;
vector v(10);
generate(v.begin(), v.end(), [&seed](){return seed++;});

array_view av(10, v);
parallel_for_each(av.extent, [=](index<1> idx) restrict (amp) {
av[idx]+=2;
});
av.synchronize();

for_each(v.cbegin(), v.cend(), [](int element){
wcout << element << endl; }); return 1; } [/code] Como pode ver, não há mais necessidade de realizar a cópia "na unha". No lugar disso, utilizamos o método synchronize para garantir que os dados presentes da GPU sejam retornados para o vector. Ou seja, temos uma amarração mais forte.

Observe agora essa nova versão:

#include
#include
#include

using namespace concurrency;
using namespace std;

void add2(vector& source)
{
array_view av(source.size(), source);
parallel_for_each(av.extent, [=](index<1> idx) restrict (amp) {
av[idx]+=2;
});
}

int main() {

float seed = 0;
vector v(10);
generate(v.begin(), v.end(), [&seed](){return seed++;});

add2(v);

for_each(v.cbegin(), v.cend(), [](int element){
wcout << element << endl; }); return 1; } [/code] Dessa vez, não foi necessário usar o método synchronize. Isso ocorre porque objetos do tipo array_view, quando saem de escopo, realizam essa chamada automaticamente.

Concluindo

C++ AMP trata do processamento de dados em algoritmos massivamente paralelizáveis, utilizando, geralmente, uma GPU. Para que isso ocorra, os dados precisam estar disponíveis para essa accelerator. A forma mais comum disso ocorrer é através das classes  array e array_view.

array_view é uma forma mais econômica de uso quando precisamos manter uma "versão CPU" dos dados.

Era isso.