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

Introdução a Tiling com C++ AMP - Parte 4 (final) - tile_static e tile_barrier

Tempo de leitura: 2 minutos

Olá. Tudo certo?

Este é o quarto e último post de uma pequena série introdutória sobre tiling com C++ AMP. Antes de seguir a leitura, recomendo que você leia os posts anteriores:

  1. motivações
  2. tiled_extent
  3. tiled_index

Hoje, exploraremos, finalmente, o conceito central de tiling:  compartilhamento de dados, utilizando caching, entre threads. O exemplo que irei utilizar é baseado naquele utilizado no excelente vídeo introdutório sobre tiling, disponível no channel9, produzido pelo Daniel Roth. Aliás, a esta altura, com o conhecimento que você já tem sobre tiling, ficará fácil entender o que ele explica por lá.

Sobre tile_static

tile_static é a segunda modificação promovida na linguagem C++ para habilitar C++ AMP (a outra é o meta-atributo restrict(amp)). Trata-se de um modificador que torna o valor de uma variável criada em uma thread, acessível a todas as outras que estejam no mesmo tile.

Sobre tile_barrier

tile_barrier é um mecanismo de sincronização de execução, entre threads que estejam em um mesmo tile.

Código de exemplo

Finalmente, vamos a um exemplo que utilize tiling. Como indicado pelo autor original do código que uso como referência, não se trata de algo útil de verdade. É apenas algo "fácil de entender".

Vejamos:

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

using namespace concurrency;

int main()
{
	std::vector<int> m(2*6);
	m[0]=130; m[1]=140; m[2]=150; m[3]=160; m[4] =170; m[5] =180;
	m[6]=290; m[7]=316; m[8]=342; m[9]=368; m[10]=394; m[11]=420;

	array_view<int, 2> av(2, 6, m);
	parallel_for_each(
		av.extent.tile<2,2>(),
		[=](tiled_index<2, 2> t_idx) restrict (amp) {

			tile_static int b[2][2];
			b[t_idx.local[0]][t_idx.local[1]] = av[t_idx.global];

			t_idx.barrier.wait();

			if (t_idx.local == index<2>(0,0))
			{
				av[t_idx.global] = b[0][0] + b[1][0] + b[0][1] + b[1][1];
			}

		});

	int sum = av(0, 0) + av(0, 2) + av(0, 4);

	assert(sum == 3060);
}

Esse código, embora inútil pode ser bastante instrutivo. A ideia, se você ainda não pegou, é calcular a soma dos elementos do vetor.

Para começar, os dados são disponibilizados para a GPU através de um array_view. Este, por sua vez, é particionado em três tiles de 2x2 (quatro elementos cada).

A variável b, marcada como tiled_static, é armazenada em um caching da GPU. Ou seja, fora da memória global. O valor dessa variável é preservado para todas as threads que façam parte da thread. No nosso exemplo, fazemos com que cada thread copie seu valor valor correspondente da memória global da GPU para o cache. Os índices globais são utilizados para acessar o valor na array_view. Os índices locais são utilizados para o armazenamento no cache.

O tile_barrier, fornecido pelo objeto tiled_index, trata-se de um mecanismo simples de sincronização. A infraestrutura do C++ AMP garante que todas as threads tenham sua execução interrompida até que todas as demais, no mesmo tile, atinjam esse ponto de execução.

Por fim, fazemos com que apenas uma das threads (poderia ser qualquer uma), de cada tile execute um somatório dos valores do tile que é, posteriormente, persistido no array_view.

Concluindo

Tiling é um mecanismo poderoso, provido pelo C++ AMP, para caching e compartilhamento de dados entre threads.

No futuro, irei demonstrar algumas versões "modificadas" de algoritmos massivamente paralelizáveis que fazem uso desse caching e se beneficiam das vantagens do seu acesso "mais barato".

Era isso.

Enviar um comentário