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

Fundamentos de C++ AMP - Parte 2 - accelerator e accelerator_view

Tempo de leitura: 1 minuto

Olá. Tudo certo?

Este é o segundo post da série onde apresento os fundamentos de C++ AMP. Se está chegando agora, recomendo a leitura do primeiro post.

C++ AMP facilita a execução de código massivamente paralelizável em GPUs usando C++ (quase) puro. De forma resumida, podemos dizer que ele leva dados da memória da CPU para a memória da GPU, executa processamento, e traz os dados de volta. Como foi dito no primeiro post, usar a GPU é uma excelente oportunidade para ganhos de performance "absurdos" (em alguns casos, o desempenho melhora centenas de vezes) e, além disso, consumindo menos energia.

No post de hoje, falaremos sobre dois tipos fundamentais para programação com C++ AMP:  accelerator e accelerator_view.

Relação entre accelerator e GPU

Na maioria das vezes, em C++ AMP, quando se fala em "accelerator", podemos pensar em GPU. Entretanto, GPU não é o único tipo de accelerator disponível e, além disso, nem todas as placas de vídeo tem uma GPU que pode ser utilizada como accelerator (é necessário suporte para DirectX 11).

Em C++ AMP, acceleratorque está no namespace Concurrency, é um tipo que pode descrever:

  • GPU física (em uma placa de vídeo compatível com DirectX 11);
  • emulador de software - como um distribuído com o Visual Studio para fins de depuração;
  • WARP - que é uma aceleração implementada em CPU usando recursos multicore e instruções SSE.

Obtendo a relação de accelerators disponíveis em um ambiente

O código que segue relaciona as "accelerators" disponíveis em um computador:

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

using namespace concurrency;
using namespace std;

#define YESNO(value) (value ? "YES" : "NO")

int main() {

	wcout << "Listing avaliable C++ AMP accelerators" <<
		endl << endl;

	std::vector<accelerator> accelerators = accelerator::get_all();

	if (accelerators.empty()) {
		wcout << "No accelerators found!" << endl;
		return 0;
	}

	wcout << "Found: " << accelerators.size() << endl << endl;

	int n = 0;
	for_each(accelerators.cbegin(), accelerators.cend(), &#91;&n&#93;(const accelerator& a) {
		wcout << ++n << ": "
			<< a.description << " (" << a.version << ")" << endl
			<< "           has display: "
			<< YESNO(a.has_display) << endl
			<< "           is emulated: "
			<< YESNO(a.is_emulated) << endl
			<< "      double precision: "
			<< YESNO(a.supports_double_precision) << endl
			<< " limited dbl precision: "
			<< YESNO(a.supports_limited_double_precision) << endl
			<< "           device path: "
			<< a.device_path << endl
			<< endl;
	});

	return 1;
}
&#91;/code&#93;

No código, o método <a href="http://msdn.microsoft.com/en-us/library/vstudio/hh538010.aspx">accelerator::get_all</a>, retorna um vetor com todos os accelerators disponíveis no momento da execução. Em C++ AMP, podemos optar por qual accelerator utilizar dependendo de nossas necessidades.
<h3>O <em>accelerator</em> default</h3>
Em C++ AMP, há sempre um accelerator default. Ele é definido segundo critérios que determinam que ele apresentará melhor performance. Por exemplo, se houver um accelerator que aponte para um dispositivo físico e outro que seja emulado, o primeiro será selecionado.

O código que segue apresenta os detalhes do accelerator default para um ambiente:


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

using namespace concurrency;
using namespace std;

#define YESNO(value) (value ? "YES" : "NO")

int main() {

	wcout << "Details of default C++ AMP accelerator" <<
		endl << endl;

	accelerator a;

	wcout << "Default: "
		<< a.description << " (" << a.version << ")" << endl
		<< "           has display: "
		<< YESNO(a.has_display) << endl
		<< "           is emulated: "
		<< YESNO(a.is_emulated) << endl
		<< "      double precision: "
		<< YESNO(a.supports_double_precision) << endl
		<< " limited dbl precision: "
		<< YESNO(a.supports_limited_double_precision) << endl
		<< "           device path: "
		<< a.device_path << endl
		<< endl;

	return 1;
}
&#91;/code&#93;

Como pode ver, o acelerador default é representado por padrão pela classe accelerator.
<h3><em>accelerator_view</em></h3>
Como explicado, um accelerator é, geralmente, um dispositivo físico. Em C++ AMP, é possível ter diversas "views" desse dispositivo. Essas "views" são isoladas uma da outra. Elas existem para facilitar a gestão de compartilhamento de dados entre threads em execução em um mesmo accelerator. Mais adiante, falaremos disso em detalhes.

O código que segue cria um "array" de dados no accelerator padrão, através de sua view padrão:


accelerator a;
accelerator_view av = a.default_view;
array<float> anArray(5, av);

Obviamente, não seria necessário especificar uma "view" para o accelerator default nesse construtor. Foi só um exemplo "instrutivo".

Concluindo

Nesse post, vimos que C++ AMP:

  • permite que relacionemos, em runtime, todos os "accelerators" disponíveis no ambiente de execução;
  • seleciona um accelerator, dentre os disponíveis, para ser utilizado por default - aquele que tem melhor performance;
  • permite que usemos um accelerator diferente do padrão, caso desejemos;

Também vimos que accelerators geralmente representam GPUs. Há também accelerators que estão emulando o comportamento da GPU em CPU e, em alguns casos, com performance aperfeiçoada (WARP).

Era isso.