Elemar DEV

Tecnologia e desenvolvimento

Fundamentos de PPL (C++) – Parte 1 – Tasks

Olá. Tudo certo?!

PPL (Parallel Patterns Library) é uma biblioteca C++ que objetiva facilitar o desenvolvimento de aplicações com suporte a paralelismo.

Podemos entender essa biblioteca através das seguintes iniciativas:

  1. Paralelismo de tarefas (Tasks) – que permite a execução de duas ou mais atividades simultaneamente;
  2. Algoritmos e “containers” – métodos genéricos de processamento combinados com estruturas de dados com suporte pronto para acesso e modificação em paralelo.

Em muitos aspectos, PPL lembra a TPL (Task Parallel Library) que está disponível para desenvolvedores .NET. Além disso, “algoritmos e containers” parece bem influenciado pelos padrões seguidos na STL.

Um exemplo muito simples

Para começar a entender o poder dessa biblioteca, comecemos por um exemplo extremamente simples.

#include <iostream>
#include <ppl.h>

using namespace std;
using namespace concurrency;

int fibonacci(int number)
{
	if (number < 2)
		return number;
	return fibonacci(number - 1) + fibonacci(number - 2);
}

int main()
{
	int fib35, fib40;

	parallel_invoke(
		[&] { fib35 = fibonacci(35); },
		[&] { fib40 = fibonacci(30); }
	);

	cout << "Using parallel_invoke: " << endl
		<< "fib(35) = " << fib35 << endl
		<< "fib(40) = " << fib40 << endl << endl;
}

Pegou a ideia?!

A função parallel_invoke é a “porta de entrada” para a PPL. Esta função cria uma “task group” com  uma “execução paralela” para cada expressão lambda fornecida em sua lista de argumentos.

Reproduzindo o comportamento de parallel_invoke

Podemos obter o mesmo resultado de parallel_invoke usando o objeto task_group. Veja só:

int main()
{
	int fib35, fib40;

	task_group tg;

	tg.run([&] { fib35 = fibonacci(35); });
	tg.run([&] { fib40 = fibonacci(40); });

	tg.wait();

	cout << "Using task_group: " << endl
		<< "fib(35) = " << fib35 << endl
		<< "fib(40) = " << fib40 << endl << endl;
		
	return 0;
}

Certo?

O método run cria e agenda uma nova task. O argumento desse método deve ser uma expressão lambda, ou um ponteiro para uma função, ou um “objeto” função que será executado quando a task entrar em atividade.
O método wait interrompe a execução do fluxo atual até que todas as tarefas tenham sido executadas.

Colocando tudo junto

Para concluirmos, vejamos um comparativo entre as abordagens e a execução não paralela.

#include <Windows.h>
#include <iostream>
#include <ppl.h>

using namespace std;
using namespace concurrency;

template <typename TFunction>
__int64 time_call(TFunction&& function)
{
	__int64 begin = GetTickCount();
	function();
	return GetTickCount() - begin;
}

int fibonacci(int number)
{
	if (number < 2)
		return number;
	return fibonacci(number - 1) + fibonacci(number - 2);
}

void NoParallel()
{
	int fib35, fib40;

	fib35 = fibonacci(35);
	fib40 = fibonacci(40);

	cout << "No Parallel: " << endl
		<< "fib(35) = " << fib35 << endl
		<< "fib(40) = " << fib40 << endl << endl;
}

void UsingParallelInvoke()
{
	int fib35, fib40;

	parallel_invoke(
		[&] { fib35 = fibonacci(35); },
		[&] { fib40 = fibonacci(40); }
	);

	cout << "Using parallel_invoke: " << endl
		<< "fib(35) = " << fib35 << endl
		<< "fib(40) = " << fib40 << endl << endl;
}

void UsingTaskGroup()
{
	int fib35, fib40;

	task_group tg;

	tg.run([&] { fib35 = fibonacci(35); });
	tg.run_and_wait([&] { fib40 = fibonacci(40); });

	cout << "Using task_group: " << endl
		<< "fib(35) = " << fib35 << endl
		<< "fib(40) = " << fib40 << endl << endl;
}

int main()
{
	__int64 noParallelTime = 
		time_call(NoParallel);
	
	__int64 parallelInvokeTime = 
		time_call(UsingParallelInvoke);

	__int64 taskGroupTime = 
		time_call(UsingTaskGroup);


	cout 
		<< "No Parallel    : " << noParallelTime << "ms. " << endl 
		<< "Parallel Invoke: " << parallelInvokeTime << "ms. " << endl
		<< "Task Group     : " << taskGroupTime  << "ms. " << endl
		;

	return 0;
}

Se executar, perceberá que a diferença de desempenho é muito pequena. Por quê? Simples! Estamos usando abordagens pouco eficientes.

Voltaremos.

Um comentário em “Fundamentos de PPL (C++) – Parte 1 – Tasks

  1. Pingback: Fundamentos de PPL (C++) – Parte 2 – parallel_for e combinable | Elemar DEV

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s

Informação

Publicado às 09/03/2013 por em Post e marcado , , , .

Estatísticas

  • 638,095 hits
%d blogueiros gostam disto: