Programação Paralela em Arquiteturas Multi-Core/Ambientes de programação e bibliotecas: diferenças entre revisões

[edição não verificada][edição não verificada]
Conteúdo apagado Conteúdo adicionado
Linha 28:
===Desenvolvendo aplicações utilizando ''parallel_for''===
 
Suponha que você quer aplicar uma função Foo para cada elemento de um arranjo, e isso é seguro processar cada elemento concorrentemente. Abaixo veremo um código sequencia para fazer isso:
Apresentaremos um exemplo básico para uso do parallel_for template em um programa de encontrar uma substring. Para cada posição na string, o programa mostra o tamanho e a localidade do maior substring encontrado na string.
 
void SerialApplyFoo (float a[], size_t n ){
Por exemplo, considere a string “babba” como um exemplo. Inicie na posição 0, “ba” é a maior substring com um a encontrado dentro da string.
for( size_t i=0;i<n; ++i )
Foo(a[i]);
}
 
A interação aqui é do tipo size_t, e vai de 0 a n-1. A função template tbb::parallel_for sai da interação no pedaço(chunk), e executa cada pedaço em uma thread separada. O primeiro passo na paralelização desse loop é converter o corpo do loop em uma forma que opere em um pedaço. Essa forma é em uma função STL (STL-style), chamado o corpo, no qual o operator() processa um dos pedaços(chunk). O seguinte codigo declara o coropo. O codigo extra requirido para o TBB é mostrado em azul.
Para desenvolver o código exemplo:
#include "tbb/blocked_range.h"
* Crie uma aplicação vazia. Na função main, inicialize a agenda da tarefa (task scheduler) pela instanciação do objeto task_scheduler_init(linha 37).
class ApplyFoo{
* Qualquer thread que usa um algoritmo TBB deve ter inicializado o objeto task_scheduler_init. Nesse exemplo, o construtor padrão para o objeto task_scheduler_init informa a agenda da tarefa (task scheduler) que a thread esta participando na execução da tarefa e o destrutor informa a agenda (scheduler) que a thread não mais precisa dela.
float *const my_a;
* A definição para a classe task_scheduler_init é incluida pelo arquivo de cabeçalho task_scheduler_init.h(linha 4).
public:
* A sentença ''using'' importa o namespace tbb, do qual todas as classes da biblioteca e funções são encontradas (linha 7).
void operator()( const blocked_range<size_t>& r) const{
04: #include "tbb/task_scheduler_init.h"
float *a = my_a;
07: using namespace tbb;
for( size_t i=r.begin(); i!=r.end(); ++i )
36: int main() {
Foo(a[i]);
37: task_scheduler_init init;
47: return 0; }
ApplyFoo( float a[] ):
48: }
my_a(a)
* Crie a string exemplo que é transformada pelo programa (linhas 38-40) e o arranjo para manter o tamanho do maior substring encontrada e sua localização (linhas 41-42).
{}
};
 
Note o argumento operator(). A blocked_range<T> é uma classe template provida pela biblioteca. Ela descreve uma iteração de uma dimensão sobre o tipo T. A classe parallel_for trabalha com outros tipos de iteração também. A biblioteca provê blocked_range2d para espaços bidimensionais. Voce pode definir seu próprio espaço como explicado adiante.
O exemplo gera uma string de Fibonacci consistindo de uma serie de caracteres "a" e "b".
 
Uma instância de ApplyFoo precisa de um campo membro que o lembre de todas as variaveis que foram definidas fora do loop original mas que está sendo usada dentro dele. Usualmente, o construtor para o corpo do objeto irá iniciar esse campo, através de parallel_for que não se importa em como o corpo do objeto foi criado. A função template parallel_for requer que o corpo do objeto tenha uma copia do construtor, do qual é invocado para criar uma copia separada (ou copias_ de cada thread ativa. Isso também invoca um destrutor para destruir essas copias. Em muitos casos, a copia do construtor gerada implicitamente e o destrutor trabalham corretamente. Caso contrario, isso é sempre o caso (como usualmente em C++) que você defina ambos para serem consistentes.
* Adicione para a saída do tamanho e a localização da maior substring encontrada para cada posição (linha 45 – 46).
01: #include <iostream>
02: #include <string>
04: #include "tbb/task_scheduler_init.h"
07: using namespace tbb;
08: using namespace std;
09: static const size_t N = 23;
36: int main() {
37: task_scheduler_init init;
38: string str[N] = { string("a"), string("b") };
39: for (size_t i = 2; i < N; ++i) str[i] = str[i-1]+str[i-2];
40: string &to_scan = str[N-1];
41: size_t *max = new size_t[to_scan.size()];
42: size_t *pos = new size_t[to_scan.size()];
43—44: // will add code to populate max and pos here
45: for (size_t i = 0; i < to_scan.size(); ++i)
46: cout << " " << max[i] << "(" << pos[i] << ")" << endl;
47: return 0;
48: }
 
Devido ao fatodo do corpo do objeto ser copiado, seu operator() nao deve modifica-lo. Por outro lado a modificação deve ou não se tornar visivel para a thread que é invocou o parallel_for, dependendo se operator() esta atuando sobre o original ou sobre uma copia. Como lembrança disso, parallel_for requer que o corpo do objeto operator() seja declarado const.
* Adicione uma chamada para a função template parallel_for (linha 43-44).
 
O exemplo operator() carrega my_a em uma variavel local a. Pois não é necessario que tenha duas razões para fazer isso no exemplo:
O primeiro parâmetro da chamada é para o objeto blocked_range que descreve o espaço de iteração.
 
* '''Style:''' Faz com que o corpo do loop ose pareça com o original.
* O blocked_range é uma classe template provida pela biblioteca TBB. O construtor pega três parâmetros:
* '''Performance:''' Algumas vezes colocando frequentemente o valor acessado nas variaveis locals ajuda o compilador a otimizar o loop ainda mais, porque as variaveis locais são frequentemente mais faceis para o compilador rastrear.
** O limite inferior.
** O limite superior.
** O <grainsize> (tamanho). O parallel_for subdivide os intervalos em subintervalos que tem aproximadamente <grainsize> elementos.
 
Uma vez que voce tenha um corpo de loop escrito como um corpo de objeto, invoque a função template parallel_for, como seque:
O segundo parâmetro para a função parallel_for é o objeto a ser aplicada em cada subintervalo.
 
01: #include <iostream>"tbb/parallel_for.h"
void ParallelApplyFoo( float a[], size_t n) {
02: #include <string>
parallel_for(blocked_range<size_t>(0,n,IdealGrainSize), ApplyFoo(a) );
04: #include "tbb/task_scheduler_init.h"
}
05: #include "tbb/parallel_for.h"
06: #include "tbb/blocked_range.h"
07:using namespace tbb;
08:using namespace std;
09:static const size_t N = 23;
36:int main() {
37: task_scheduler_init init;
38: string str[N] = { string("a"), string("b") };
39: for (size_t i = 2; i < N; ++i) str[i] = str[i-1]+str[i-2];
40: string &to_scan = str[N-1];
41: size_t *max = new size_t[to_scan.size()];
42: size_t *pos = new size_t[to_scan.size()];
43: parallel_for(blocked_range<size_t>(0, to_scan.size(), 100),
44: SubStringFinder( to_scan, max, pos ) );
45: for (size_t I = 0; I < to_scan.size(); ++i)
46: cout << " " << max[i] << "(" << pos[i] << ")" << endl;
47: return 0;
48:}
 
O blocked_range construido aqui representa a iteração de entrada do espaço de 0 a n-1, do qual o parallel_for divide em subespaços para cada processador. A forma geral para o construtor blocked_range<T>(begin,end,grainsize). O T especifica o valor tipo. O argumento ''begin'' e ''end'' especifica o espaco de iteração STL-style como o intervalo (begin,end).
# Implemente o corpo do laço de parallel_for (linha 10-35). Em execução, o template parallel_for automaticamente divide o intervalo em subintervalos e invocam a função SubStringFinder em cada subintervalo.
# Defina a classe SubStringFinder (linha 10) para popular os elementos dos arranjos max e pos encontrados dentro de um dado subintervalo.
 
Na linha 16, a chamada r.begin() retorna o inicio do subintervalo e o método r.end() retorna o seu fim.
01: #include <iostream>
02: #include <string>
03: #include <algorithm>
04: #include "tbb/task_scheduler_init.h"
05: #include "tbb/parallel_for.h"
06: #include "tbb/blocked_range.h"
07: using namespace tbb;
08: using namespace std;
09: static const size_t N = 23;
10: class SubStringFinder {
11: const string str;
12: size_t *max_array;
13: size_t *pos_array;
14: public:
15: void operator() ( const blocked_range<size_t>& r ) const {
16: for ( size_t I = r.begin(); I != r.end(); ++I ) {
17: size_t max_size = 0, max_pos = 0;
18: for (size_t j = 0; j < str.size(); ++j)
19: if (j != i) {
20: size_t limit = str.size()-max(I,j);
21: for (size_t k = 0; k < limit; ++k) {
22: if (str[I + k] != str[j + k]) break;
23: if (k > max_size) {
24: max_size = k;
25: max_pos = j;
26: }
27: }
28: }
29: max_array[i] = max_size;
30: pos_array[i] = max_pos;
31: }
32: }
33: SubStringFinder(string &s, size_t *m, size_t *p) :
34: str(s), max_array(m), pos_array(p) { }
35: };
36—48:// The function main starting at line 36 goes here
 
=== Desenvolvendo aplicações utilizando ''parallel_reduce'' ===