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

Fundamentos de C++ AMP - parte 5 (final) - restrict(amp) e parallel_for_each

Tempo de leitura: 3 minutos

Olá. Tudo certo?

Este é o último post dessa "mini" série sobre fundamentos C++ AMP. O objetivo dessa série foi apresentar conceitos fundamentais dessa tecnologia para, mais tarde, fundamentar séries sobre tópicos mais avançados, como tiling, por exemplo.

Os posts anteriores dessa série foram:

  1. O que é? Para que serve? Hello World!
  2. accelerator e accelerator_view
  3. array e array_view
  4. index e extent

Nesse último post, falo sobre a modificação no C++, restrict(amp), e sobre a função parallel_for_each.

parallel_for_each

Essa função é a responsável por paralelizar todo o trabalho. Como vimos em posts anteriores, começamos configurando um array (ou array_view) e preenchendo-os com valores. Então, parallel_for_each realiza algum processamento com cada elemento.

parallel_for_each opera a partir de um extent - que determina o número de threads que realizará o trabalho - e uma função core.

Voltemos ao último exemplo do post anterior.

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

using namespace concurrency;

int main() {

	std::vector<float> vA(16);
	std::vector<float> vB(16);
	std::vector<float> vC(16);

	int seed = 0;
	std::generate(vA.begin(), vA.end(), [&](){ return ++seed; });
	std::generate(vB.begin(), vB.end(), [&](){ return seed--; });

	extent<2> e(4, 4);
	array_view<const float, 2> a(e, vA);
	array_view<const float, 2> b(e, vB);
	array_view<float, 2> c(e, vC);
	c.discard_data();

	parallel_for_each(a.extent, [=](index<2> idx) restrict (amp) {
		c[idx] += a[idx] + b[idx];
	});
	c.synchronize();

	index<2> idx(0,0);
	assert(c[idx] == 17);
}

Retomando, o primeiro argumento é o objeto extent relacionado a array / array_view. O segundo argumento é uma função lambda que irá, efetivamente, realizar todo o processamento.

Quando um array é passado para a função parallel_for_each, a paralelização será executada na accelerator_view associada.

A função lambda indicada deverá estar marcada, obrigatoriamente, como restrict(amp), que iremos detalhar em breve. Além disso:

  • a função precisa retornar void;
  • a função deverá estar preparada para receber apenas um objeto index com a mesma dimensão indicada pelo objeto extent;
  • a função lambda não poderá capturar nada por referência (exceto instâncias de objetos array);
  • a função lambda somente poderá capturar valores compatíveis com as restrições do accelerator.

restrict(amp)

Uma função precisa obedecer um conjunto claro de regras para que possa ser executada em um accelerator (leia-se GPU). restrict(amp) é uma modificação no C++ que garante que uma função obedeça a essas regras - funções marcadas com restrict(amp) irão provocar falhas de compilação caso não sejam respeitadas as restrições impostas.

Vamos a regras determinadas pelo restrict(amp):

  • são aceitos apenas os tipos int, unsigned int, float, double, arrays em estilo C dos tipos indicados, concurrency::array_view ou referência para concurrency_array e structs contendo apenas os tipos compatíveis com C++ AMP;
  • Referências e ponteiros (para um tipo compatível) devem ser usadas localmente mas não podem ser capturadas pela expressão lambda;
  • Ponteiros para funções, ponteiros para ponteiros não são permitidos;
  • Utilização de variáveis estáticas e globais não são permitidas;
  • Classes utilizadas não podem ter funções virtuais, todos os atributos devem ser compatíveis com os tipos permitidos ou classes que obedeçam as regras já descritas;
  • Apenas funções marcadas com restrict(amp) podem ser evocadas;
  • Não pode ocorrer recursão;
  • Não é autorizado armazenar valores no heap (óbvio). Logo, nada de new ou delete.

Há outras restrições. Mas, considero que estas sejam as mais importantes.

Funções matemáticas compatívels com restrict(amp)

Como mencionado, não é possível chamar uma função que não esteja marcada com restrict(amp) a partir de uma que esteja. Essa regra simples acaba restringindo um bocado nossas opções quando utilizamos a biblioteca padrão.

Entretanto, C++ AMP traz algumas centenas de funções marcadas com restrict(amp) de diversos propósitos, sobretudo para a execução de operações matemáticas. Estas últimas estão definidas em amp_math.h.

restrict(cpu)? restrict(cpu, amp)?

Toda função que não esteja marcada como restrict(amp) está marcada, implicitamente, como restrict(cpu). Ou seja, terá código que poderá rodar na CPU.

A marcação restrict(cpu,amp) indicará compatibilidade para execução tanto na CPU quanto em um accelerator (leia-se GPU).

Concluindo

Essa "mini" série objetiva apenas apresentar conceitos fundamentais de C++ AMP. Ela deverá servir como referência para posts que pretendo escrever no futuro. Espero que ela tenha lhe ajudado.

Se você ainda tem dificuldades para trabalhar (ou entender) C++, considere dar uma olhada em outras séries introdutórias que estão relacionadas no índice de séries aqui do blog.

Era isso. Até a próxima!

Enviar um comentário