Программирование для нескольких ядер PPL

Рубрика: Visual Studio, Дата: 16 August, 2014, Автор:

Схема параллельних потоковЗдорова Господа!

В этой статье мы разберем, что такое параллельное программирование и как может программа параллельно выполняться. Я давно хотел эту тему поднять, но все никак она мне не попадалась в книгах, а так разбирать не охота было, а сейчас дошел я до этой темы и так мы будем рассматривать библиотеку PPL.

Содержание:

1. PPL (Parallel Patterns Library)
2. Алгоритмы параллельной обработки
3. Алгоритм parallel_for
4. Алгоритм parallel_for_each
5. Использование алгоритма parallel_invoke
6. Шаблон класса combinable
7. Объект класса task_group
8. Объект structured_task_group
9. Отображение набора Мандельброта
10. Рисование набора Мандельброта с помощью алгоритма parallel_invoke.
11. Три решенные задачи по PPL .


PPL (Parallel Patterns Library)  –  это так называемая библиотека шаблонов для параллельных вычислений.  Определена в файле ppl.h. Инструменты разработчика находятся в пространстве имен Concurrency.

Библиотека PPL определяет три вида средств параллельной обработки

  • Шаблоны для алгоритмов параллельных операций.
  • Шаблон класса для обслуживания совместно используемых ресурсов
  • Шаблоны класса для управления и группировки параллельных задач.

Алгоритмы параллельной обработки

Библиотека PPL предоставляет три алгоритма инициализации параллельной обработки на несколько ядрах.

  • Алгоритм parallel for – эквивалент цикла for, выполняющий итерации цикла параллельно.
  • Алгоритм parallel_for_each параллельно выполняет повторяющиеся операции с контейнером STL.
  • Алгоритм parallel_invoke параллельно выполняет набор из двух или нескольких независимых задач.

Рассмотрим как используется каждый алгоритм.

Алгоритм parallel_for

 Разсмотрим пример

#include <iostream>
using std::cout;
using std::endl;
#include "ppl.h"

int main()
{
	//возвести индексы элементов массива в квадрат используя ppl
	const int count(100000);
	int mass[count];
	Concurrency::parallel_for(0,count,
		[&mass](int n)->void//лямбда выражение
	{
		mass[n]=n*n;
	});

	for(int i=0;i<10;i++)
		cout <<"i= "<<i<<" mass["<<i<<"]= "<<mass[i]<<endl;

	return 0;
}

Вывод:

Вывод программыВ коде выше мы рассчитываем используя PPL кубы от 0 до 100000 элементов, я выводить все элементы стал, а только 10, чтобы показать, что программа корректно работает, вычисление в программе идет параллельно.

И еще в функцию parralel_for можно добавлять шаг, тоесть избирать элементы для которых будет рассчитываться куб, например для каждого третьего элемента.

#include <iostream>
using std::cout;
using std::endl;
#include "ppl.h"

int main()
{
	//возвести индексы элементов массива в квадрат используя ppl
	const int count(100000);
	int mass[count]={0};
	Concurrency::parallel_for(0,count,3,
		[&mass](int n)->void//лямбда выражение
	{
		mass[n]=n*n;
	});

	for(int i=0;i<10;i++)
		cout <<"i= "<<i<<" mass["<<i<<"]= "<<mass[i]<<endl;

	return 0;
}

вывод будет уже другой чуток:

ppl_enter_three

Вывод уже другой как видно из рисунка выше. Мы просто добавили еще один параметр шаг и вызвали перегруженную функцию с шагом.

Рассмотрим следующий алгоритм parallel_for_each

Алгоритм parallel_for_each

Алгоритм parallel_for_each подобен алгоритму parallel_for, но отличия в том, что данная функция работает с контейнерами STL и принимает в качестве параметров итераторы. От простой примерчик использования parallel_for_each.

#include <iostream>
using std::cout;
using std::endl;
#include <array>
using std::array;
#include <string>
using std::string;
#include <algorithm>
using std::for_each;
#include "ppl.h"//подключаем библиотеку шаблонов

int main()
{
	array<string, 5>mass={"hellow world", "ebta mazafaka",
		"beny benasy", "supper pupper", "leddy gaga"};
	cout <<"before:"<<endl;
	for_each(mass.begin(),mass.end(),
		[](string s)
	{
		cout <<"s= "<<s<<endl;
	});
	cout <<endl;

	Concurrency::parallel_for_each(mass.begin(),mass.end(),
		[](string& s)->void
	{
		size_t pos=s.find(' ');//находим позицию пробела
		string s1=s.substr(0,pos);
		string s2=s.substr(pos+1,s.length()-1);
		s=s2+" "+s1;
	});

	for_each(mass.begin(),mass.end(),
		[](string s)->void
	{
		cout <<"s= "<<s<<endl;
	});

	return 0;
}

вывод:

вывод из программыКак видим примерчик меняет местами слова в словосочетаниях, все это он делает параллельно, используя параллельные вычисления.

И еще рассмотрим примерчик, как из paralle_for можно вызвать исключение и обработать

#include <iostream>
using std::cout;
using std::endl;
#include <array>
using std::array;
#include <string>
using std::string;
#include <algorithm>
using std::for_each;
#include "ppl.h"//подключаем библиотеку шаблонов

int findIndex(array<string,5> mass,string name);

int main()
{
	array<string, 5>mass={"hellow world", "ebta mazafaka",
		"beny benasy", "supper pupper", "leddy gaga"};
	cout <<"before:"<<endl;
	for_each(mass.begin(),mass.end(),
		[](string s)
	{
		cout <<"s= "<<s<<endl;
	});
	cout <<endl;

	Concurrency::parallel_for_each(mass.begin(),mass.end(),
		[](string& s)->void
	{
		size_t pos=s.find(' ');//находим позицию пробела
		string s1=s.substr(0,pos);
		string s2=s.substr(pos+1,s.length()-1);
		s=s2+" "+s1;
	});

	cout <<"after:"<<endl;
	for_each(mass.begin(),mass.end(),
		[](string s)->void
	{
		cout <<"s= "<<s<<endl;
	});

	string name="gaga";
	int a=findIndex(mass,name);
	if(a==-1)
		cout <<"not found name"<<endl;
	else
		cout <<"mass["<<a<<"]= "<<mass[a]<<endl;

	return 0;
}

int findIndex(array<string,5> mass,string name)
{
	int begin(0);
	int end=mass.size();

	try
	{
		Concurrency::parallel_for(begin,end,
			[=](int n)->void
		{
			int pos=mass[n].find(' ');
			string s1=mass[n].substr(0,pos);
			if(name==s1)//если найдено первое слово, то возвращаем индекс
				throw n;
		});
	}
	catch(int& index)
	{
		cout <<"index= "<<index<<endl;
		return index;
	}

	return -1;
}

Вывод:

ppl 170В коде выше алгоритм parallel_for_each меняет местами имена и фамилии в каждом элементе массива mass, используя параллельные операции. Вывод, созданный алгоритмом STL for_each, демонстрирует, что все работает так, как нада.

Затем мы создаем искомую строку name. Переменная a инициализируется значением, возвращенным функцией findIndex(). Оператор if выводит сообщение в зависимости от значения переменной a, и последняя строка вывода демонстрирует что значение переменной name действительно было найдено в массиве mass.

Использование алгоритма parallel_invoke

Алгоритм parallel_invoke определяется шаблоном, который позволяет решать параллельно до десяти задачь. Вполне очевидно, что нет никакого смысла пытаться решать параллельно больше задач, чем есть процессоров на вашем компьютере. Аргументы функции parallel_invoke – это объекты функции, определяющие задачи, которые будут решаться параллельно. Рассмотрим пример.

#include <iostream>
using std::cout;
using std::endl;
#include <array>
using std::array;
#include "ppl.h";

int main()
{
	array<int, 10> kvadrat;
	array<int, 10> cub;

	Concurrency::parallel_invoke(
		[&kvadrat]()->void
	{
		for(int i=0;i<kvadrat.size();i++)
			kvadrat[i]=i*i;
	},
		[&cub]()->void
	{
		for(int i=0;i<cub.size();i++)
			cub[i]=i*i*i;
	}

		);

	//вывод результатов
	for(int i=0;i<kvadrat.size();i++)
	{
		cout <<"i= "<<i<<" kvadrat["<<i<<"]= "<<kvadrat[i]
		<<" cub["<<i<<"]= "<<cub[i]<<endl;
	}

	return 0;
}

вывод:

parallel_invoke

В примере выше выполняются две функции параллельно, одна из них подсчитывает куб, а вторая квадрат числа.

Приведенные здесь примеры и фрагменты кода не были теми вычислениями, которые могли бы извлечь реальную пользу от параллельного выполнения.

Шаблон класса combinable

combinable предназначен для помощи для помощи в совместном использовании ресурсов, которые могут быть сегментированы в локальные переменные для потоков и объединены после завершения вычисления.

Разсмотрим пример использования combinable для подсчета количества суммы элементов в массиве.

#include <iostream>
using std::cout;
using std::endl;
#include <array>
using std::array;
#include "ppl.h"

int main()
{
	array<double, 10> mass;
	Concurrency::combinable<double> sums;
	Concurrency::parallel_for(static_cast<size_t>(0),mass.size(),
		[&mass,&sums](size_t i)->void
	{
		mass[i]=i;
		sums.local()+=mass[i];//создаем локальные переменные сум
	});
	//объединяем значение локальных переменных в одну
	double sum=sums.combine(
		[](double s1,double s2)->double
	{
		return s1+s2;
	});

	cout <<"sum= "<<sum<<endl;

	return 0;
}

вывод:

ppl 171в коде выше все ясно, что там происходит. Создается класс combinable<double> sums;, затем в лямбда выражении в строчке sums.local()+=mass[i]; создаются две локальные суммы, как бы они параллельно создаются. А дальше в строке

double sum=sums.combine(
		[](double s1,double s2)->double
	{
		return s1+s2;
	});

из двух локальных переменных сумма записывается в переменную doulbe sum.

Объект класса task_group

Вы можете использовать объект task_group для параллельного решения двух и более задач, где задача может быть определена функтором или  лямбда-выражением. Объект task_group потокобезопасен, таким  образом, можете использовать его для инициализации задачи для разных потоков.

Рассмотрим пример кода в котором параллельно подсчитывается сумма в двух массивах с использованием объекта task_group.

#include <iostream>
using std::cout;
using std::endl;
#include <array>
using std::array;
#include <algorithm>
using std::generate;
#include "ppl.h"

int main()
{
	array<int, 50> chet;
	array<int, 50> nechet;
	int n(0);
	generate(chet.begin(),chet.end(),
		[&n]()->int
	{
		return ++n*2;
	});
	n=0;
	generate(nechet.begin(),nechet.end(),
		[&n]()->int
	{
		return ++n*2-1;
	});

	int sumChet(0), sumNeChet(0);
	Concurrency::task_group task;
	//определяем функцию которая будет выполнятся параллельно
	//в другом процессе.
	task.run(
		[&sumChet,&chet]
	{
		for(int i=0;i<chet.size();++i)
		{
			sumChet+=chet[i];
		}
	});

	for(int i=0;i<nechet.size();++i)
	{
		sumNeChet+=nechet[i];
	}

	task.wait();//ждем пока досчитается задание

	cout <<"sumChet= "<<sumChet<<endl;
	cout <<"sumNeChet= "<<sumNeChet<<endl;

	return 0;
}

вывод:

ppl 172в коде есть подробные комментарии подробно описывающие как работает код. В функции run можно определить несколько лямбда – выражений, в данном примере мы определили одно лямбда выражение, и оно выполняется отдельным процессом, идущий следом цикл for выполняется уже в том процессе в котором выполняется сама программа.

Объект structured_task_group

Данный объект может использоваться только для инициализации задачи одного потока. В отличие от объектов task_group вы не сможете передать лямбда-выражение или объект функции в функцию run() для объекта structured_task_group.

Извините но пример который был в книге у меня почему то не заработал, поэтому я пока этот объект пропустю.

Отображение набора Мандельброта

И так рассмотрим отображение набора Мандельброта, от есть код на winapi с помощью которого выводится окно с набором Мандельброта.

Показать »

// test444.cpp: определяет точку входа для приложения.
//

#include "stdafx.h"
#include "test444.h"

#define MAX_LOADSTRING 100

// Глобальные переменные:
HINSTANCE hInst;								// текущий экземпляр
TCHAR szTitle[MAX_LOADSTRING];					// Текст строки заголовка
TCHAR szWindowClass[MAX_LOADSTRING];			// имя класса главного окна
const size_t MaxIterations(8000);       // Maximum iterations before infinity

// Отправить объявления функций, включенных в этот модуль кода:
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);//функция обработки сообщений для главного окна
INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);//функция обработки сообщений для дочернего окна

//собственные объявления функций
size_t IteratorPoint(double zReal, double zImaginary, double cReal, double cImaginary);
COLORREF Color(int n);//выбор цвета пикселя на основании n
void DrawSet(HWND hwnd);//Нарисовать набор Мандельброта

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

 	// TODO: разместите код здесь.
	MSG msg;
	HACCEL hAccelTable;

	// Инициализация глобальных строк
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_TEST444, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// Выполнить инициализацию приложения:
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}

	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TEST444));

	// Цикл основного сообщения:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int) msg.wParam;
}

//
//  ФУНКЦИЯ: MyRegisterClass()
//
//  НАЗНАЧЕНИЕ: регистрирует класс окна.
//
//  КОММЕНТАРИИ:
//
//    Эта функция и ее использование необходимы только в случае, если нужно, чтобы данный код
//    был совместим с системами Win32, не имеющими функции RegisterClassEx'
//    которая была добавлена в Windows 95. Вызов этой функции важен для того,
//    чтобы приложение получило "качественные" мелкие значки и установило связь
//    с ними.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TEST444));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_TEST444);
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassEx(&wcex);
}

//
//   ФУНКЦИЯ: InitInstance(HINSTANCE, int)
//
//   НАЗНАЧЕНИЕ: сохраняет обработку экземпляра и создает главное окно.
//
//   КОММЕНТАРИИ:
//
//        В данной функции дескриптор экземпляра сохраняется в глобальной переменной, а также
//        создается и выводится на экран главное окно программы.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Сохранить дескриптор экземпляра в глобальной переменной

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      100, 100, 800, 600, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  ФУНКЦИЯ: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  НАЗНАЧЕНИЕ:  обрабатывает сообщения в главном окне.
//
//  WM_COMMAND	- обработка меню приложения
//  WM_PAINT	-Закрасить главное окно
//  WM_DESTROY	 - ввести сообщение о выходе и вернуться.
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
	case WM_COMMAND:
		wmId    = LOWORD(wParam);
		wmEvent = HIWORD(wParam);
		// Разобрать выбор в меню:
		switch (wmId)
		{
		case IDM_ABOUT:
			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
			break;
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		// TODO: добавьте любой код отрисовки...
		DrawSet(hWnd);
		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

// Обработчик сообщений для окна "О программе".
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

size_t IteratePoint(double zReal, double zImaginary, double cReal,
	double cImaginary)
{
	double zReal2(0.0),zImaginary2(0.0);//компонент квадрата z
	size_t n(0);
	for(;n<MaxIterations;++n)//Итерация уравнения Zn=Zn-1**2+K
	{//для набора Мандельброта K=c и Z0=c
		zReal2=zReal*zReal;
		zImaginary2=zImaginary*zImaginary;
		if(zReal2+zImaginary2>4)//Если дистанция от начала > 2?
			break;//точка уйдет в бесконечность, на этом закончить

		//Вычислить следующее значение Z
		zImaginary=2*zReal*zImaginary+cImaginary;
		zReal=zReal2-zImaginary2+cReal;
	}
	return n;
}

COLORREF Color(int n)
{
	if(n==MaxIterations) return RGB(0,0,0);

	const int nColors=16;
	switch(n%nColors)
	{
		case 0: return RGB(100,100,100);
		case 1: return RGB(100,0,0);
		case 2: return RGB(200,0,0);
		case 3: return RGB(100,100,0);
		case 4: return RGB(200,100,0);
		case 5: return RGB(200,200,0);
		case 6: return RGB(0,200,0);
		case 7: return RGB(0,100,100);
		case 8: return RGB(0,200,100);
		case 9: return RGB(0,100,200);
		case 10: return RGB(0,200,200);
		case 11: return RGB(0,0,200);
		case 12: return RGB(100,0,100);
		case 13: return RGB(200,0,100);
		case 14: return RGB(100,0,200);
		case 15: return RGB(200,0,200);
		default: return RGB(200,200,200);
	};
}

void DrawSet(HWND hWnd)
{
  // Get client area dimensions, which will be the image size
  RECT rect;
  GetClientRect(hWnd, &rect);
  int imageHeight(rect.bottom);
  int imageWidth(rect.right);

  // Create bitmap for one row of pixels in image
  HDC hdc(GetDC(hWnd));                  // Get device context
  HDC memDC = CreateCompatibleDC(hdc);   // Get device context to draw pixels
  HBITMAP bmp = CreateCompatibleBitmap(hdc, imageWidth, 1);
  HGDIOBJ oldBmp = SelectObject(memDC, bmp);   // Selct bitmap into DC

  // Client area axes
  const double realMin(-2.1);       // Minimum real value
  double imaginaryMin(-1.3);        // Minimum imaginary value
  double imaginaryMax(+1.3);        // Maximum imaginary value

  // Set maximum imaginary so axes are the same scale
  double realMax(realMin+(imaginaryMax-imaginaryMin)*imageWidth/imageHeight);

  // Get scale factors to convert pixel coordinates
  double realScale((realMax-realMin)/(imageWidth-1)); 
  double imaginaryScale((imaginaryMax-imaginaryMin)/(imageHeight-1));

  double cReal(0.0), cImaginary(0.0);   // Stores c components
  double zReal(0.0), zImaginary(0.0);   // Stores z components

  for(int y = 0 ; y < imageHeight ; ++y)             // Iterate over image rows
  {
    zImaginary = cImaginary = imaginaryMax - y*imaginaryScale;
    for(int x = 0 ; x < imageWidth ; ++x)            // Iterate over pixels in a row
    {
      zReal = cReal = realMin + x*realScale;
      // Set current pixel color based on n
      SetPixel(memDC, x, 0, Color(IteratePoint(zReal, zImaginary, cReal, cImaginary)));
    }
    // Transfer pixel row to client area device context
    BitBlt(hdc, 0, y, imageWidth, 1, memDC, 0, 0, SRCCOPY);
  }
  SelectObject(memDC, oldBmp);
  DeleteObject(bmp);                       // Delete bitmap
  DeleteDC(memDC);                         // and our working DC
  ReleaseDC(hWnd, hdc);                    // Release client area DC
}

Вывод:

набор мальдербротаот эту в эту программку можно оптимизировать с помощью библиотеки PPL, что б быстрее выводился этот набор.

Рисование набора Мандельброта с помощью алгоритма parallel_invoke

Нам нужно создать новую версию функции DrawSet() по имени DrawSetParallel(), без замены исходной. Сохраним обе версии в исходном коде примера

код

Показать »

// test444.cpp: определяет точку входа для приложения.
//

#include "stdafx.h"
#include "test444.h"
#include "ppl.h"
#include <functional>

#define MAX_LOADSTRING 100

// Глобальные переменные:
HINSTANCE hInst;								// текущий экземпляр
TCHAR szTitle[MAX_LOADSTRING];					// Текст строки заголовка
TCHAR szWindowClass[MAX_LOADSTRING];			// имя класса главного окна
const size_t MaxIterations(8000);       // Maximum iterations before infinity

// Отправить объявления функций, включенных в этот модуль кода:
ATOM				MyRegisterClass(HINSTANCE hInstance);
BOOL				InitInstance(HINSTANCE, int);
LRESULT CALLBACK	WndProc(HWND, UINT, WPARAM, LPARAM);//функция обработки сообщений для главного окна
INT_PTR CALLBACK	About(HWND, UINT, WPARAM, LPARAM);//функция обработки сообщений для дочернего окна

//собственные объявления функций
size_t IteratorPoint(double zReal, double zImaginary, double cReal, double cImaginary);
COLORREF Color(int n);//выбор цвета пикселя на основании n
void DrawSet(HWND hwnd);//Нарисовать набор Мандельброта
void DrawSetParallel(HWND hwnd);//использование алгоритма parallel_invoke

int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);

 	// TODO: разместите код здесь.
	MSG msg;
	HACCEL hAccelTable;

	// Инициализация глобальных строк
	LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
	LoadString(hInstance, IDC_TEST444, szWindowClass, MAX_LOADSTRING);
	MyRegisterClass(hInstance);

	// Выполнить инициализацию приложения:
	if (!InitInstance (hInstance, nCmdShow))
	{
		return FALSE;
	}

	hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TEST444));

	// Цикл основного сообщения:
	while (GetMessage(&msg, NULL, 0, 0))
	{
		if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int) msg.wParam;
}

//
//  ФУНКЦИЯ: MyRegisterClass()
//
//  НАЗНАЧЕНИЕ: регистрирует класс окна.
//
//  КОММЕНТАРИИ:
//
//    Эта функция и ее использование необходимы только в случае, если нужно, чтобы данный код
//    был совместим с системами Win32, не имеющими функции RegisterClassEx'
//    которая была добавлена в Windows 95. Вызов этой функции важен для того,
//    чтобы приложение получило "качественные" мелкие значки и установило связь
//    с ними.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
	WNDCLASSEX wcex;

	wcex.cbSize = sizeof(WNDCLASSEX);

	wcex.style			= CS_HREDRAW | CS_VREDRAW;
	wcex.lpfnWndProc	= WndProc;
	wcex.cbClsExtra		= 0;
	wcex.cbWndExtra		= 0;
	wcex.hInstance		= hInstance;
	wcex.hIcon			= LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TEST444));
	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW+1);
	wcex.lpszMenuName	= MAKEINTRESOURCE(IDC_TEST444);
	wcex.lpszClassName	= szWindowClass;
	wcex.hIconSm		= LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

	return RegisterClassEx(&wcex);
}

//
//   ФУНКЦИЯ: InitInstance(HINSTANCE, int)
//
//   НАЗНАЧЕНИЕ: сохраняет обработку экземпляра и создает главное окно.
//
//   КОММЕНТАРИИ:
//
//        В данной функции дескриптор экземпляра сохраняется в глобальной переменной, а также
//        создается и выводится на экран главное окно программы.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   HWND hWnd;

   hInst = hInstance; // Сохранить дескриптор экземпляра в глобальной переменной

   hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      100, 100, 800, 600, NULL, NULL, hInstance, NULL);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

//
//  ФУНКЦИЯ: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  НАЗНАЧЕНИЕ:  обрабатывает сообщения в главном окне.
//
//  WM_COMMAND	- обработка меню приложения
//  WM_PAINT	-Закрасить главное окно
//  WM_DESTROY	 - ввести сообщение о выходе и вернуться.
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	int wmId, wmEvent;
	PAINTSTRUCT ps;
	HDC hdc;

	switch (message)
	{
	case WM_COMMAND:
		wmId    = LOWORD(wParam);
		wmEvent = HIWORD(wParam);
		// Разобрать выбор в меню:
		switch (wmId)
		{
		case IDM_ABOUT:
			DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
			break;
		case IDM_EXIT:
			DestroyWindow(hWnd);
			break;
		default:
			return DefWindowProc(hWnd, message, wParam, lParam);
		}
		break;
	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		// TODO: добавьте любой код отрисовки...
		DrawSetParallel(hWnd);
		EndPaint(hWnd, &ps);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

// Обработчик сообщений для окна "О программе".
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(lParam);
	switch (message)
	{
	case WM_INITDIALOG:
		return (INT_PTR)TRUE;

	case WM_COMMAND:
		if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
		{
			EndDialog(hDlg, LOWORD(wParam));
			return (INT_PTR)TRUE;
		}
		break;
	}
	return (INT_PTR)FALSE;
}

size_t IteratePoint(double zReal, double zImaginary, double cReal,
	double cImaginary)
{
	double zReal2(0.0),zImaginary2(0.0);//компонент квадрата z
	size_t n(0);
	for(;n<MaxIterations;++n)//Итерация уравнения Zn=Zn-1**2+K
	{//для набора Мандельброта K=c и Z0=c
		zReal2=zReal*zReal;
		zImaginary2=zImaginary*zImaginary;
		if(zReal2+zImaginary2>4)//Если дистанция от начала > 2?
			break;//точка уйдет в бесконечность, на этом закончить

		//Вычислить следующее значение Z
		zImaginary=2*zReal*zImaginary+cImaginary;
		zReal=zReal2-zImaginary2+cReal;
	}
	return n;
}

COLORREF Color(int n)
{
	if(n==MaxIterations) return RGB(0,0,0);

	const int nColors=16;
	switch(n%nColors)
	{
		case 0: return RGB(100,100,100);
		case 1: return RGB(100,0,0);
		case 2: return RGB(200,0,0);
		case 3: return RGB(100,100,0);
		case 4: return RGB(200,100,0);
		case 5: return RGB(200,200,0);
		case 6: return RGB(0,200,0);
		case 7: return RGB(0,100,100);
		case 8: return RGB(0,200,100);
		case 9: return RGB(0,100,200);
		case 10: return RGB(0,200,200);
		case 11: return RGB(0,0,200);
		case 12: return RGB(100,0,100);
		case 13: return RGB(200,0,100);
		case 14: return RGB(100,0,200);
		case 15: return RGB(200,0,200);
		default: return RGB(200,200,200);
	};
}

void DrawSet(HWND hWnd)
{
  // Get client area dimensions, which will be the image size
  RECT rect;
  GetClientRect(hWnd, &rect);
  int imageHeight(rect.bottom);
  int imageWidth(rect.right);

  // Create bitmap for one row of pixels in image
  HDC hdc(GetDC(hWnd));                  // Get device context
  HDC memDC = CreateCompatibleDC(hdc);   // Get device context to draw pixels
  HBITMAP bmp = CreateCompatibleBitmap(hdc, imageWidth, 1);
  HGDIOBJ oldBmp = SelectObject(memDC, bmp);   // Selct bitmap into DC

  // Client area axes
  const double realMin(-2.1);       // Minimum real value
  double imaginaryMin(-1.3);        // Minimum imaginary value
  double imaginaryMax(+1.3);        // Maximum imaginary value

  // Set maximum imaginary so axes are the same scale
  double realMax(realMin+(imaginaryMax-imaginaryMin)*imageWidth/imageHeight);

  // Get scale factors to convert pixel coordinates
  double realScale((realMax-realMin)/(imageWidth-1)); 
  double imaginaryScale((imaginaryMax-imaginaryMin)/(imageHeight-1));

  double cReal(0.0), cImaginary(0.0);   // Stores c components
  double zReal(0.0), zImaginary(0.0);   // Stores z components

  for(int y = 0 ; y < imageHeight ; ++y)             // Iterate over image rows
  {
    zImaginary = cImaginary = imaginaryMax - y*imaginaryScale;
    for(int x = 0 ; x < imageWidth ; ++x)            // Iterate over pixels in a row
    {
      zReal = cReal = realMin + x*realScale;
      // Set current pixel color based on n
      SetPixel(memDC, x, 0, Color(IteratePoint(zReal, zImaginary, cReal, cImaginary)));
    }
    // Transfer pixel row to client area device context
    BitBlt(hdc, 0, y, imageWidth, 1, memDC, 0, 0, SRCCOPY);
  }
  SelectObject(memDC, oldBmp);
  DeleteObject(bmp);                       // Delete bitmap
  DeleteDC(memDC);                         // and our working DC
  ReleaseDC(hWnd, hdc);                    // Release client area DC
}

// Draws the Mandelbrot set
void DrawSetParallel(HWND hWnd)
{
  HDC hdc(GetDC(hWnd));                  // Get device context

  // Get client area dimensions, which will be the image size
  RECT rect;
  GetClientRect(hWnd, &rect);
  int imageHeight(rect.bottom);
  int imageWidth(rect.right);

  // Create bitmap for one row of pixels in image
  HDC memDC1 = CreateCompatibleDC(hdc);   // Get device context to draw pixels
  HDC memDC2 = CreateCompatibleDC(hdc);   // Get device context to draw pixels
  HBITMAP bmp1 = CreateCompatibleBitmap(hdc, imageWidth, 1);
  HBITMAP bmp2 = CreateCompatibleBitmap(hdc, imageWidth, 1);
  HGDIOBJ oldBmp1 = SelectObject(memDC1, bmp1);   // Select bitmap into DC1
  HGDIOBJ oldBmp2 = SelectObject(memDC2, bmp2);   // Select bitmap into DC2

  // Client area axes
  const double realMin(-2.1);       // Minimum real value
  double imaginaryMin(-1.3);        // Minimum imaginary value
  double imaginaryMax(+1.3);        // Maximum imaginary value

  // Set maximum real so axes are the same scale
  double realMax(realMin+(imaginaryMax-imaginaryMin)*imageWidth/imageHeight);

  // Get scale factors to convert pixel coordinates
  double realScale((realMax-realMin)/(imageWidth-1)); 
  double imaginaryScale((imaginaryMax-imaginaryMin)/(imageHeight-1));

    // Lambda expression to create an image row
    std::function<void(HDC&, int)> rowCalc = [&] (HDC& memDC, int yLocal) 
    {
      double zReal(0.0), cReal(0.0);
      double zImaginary(imaginaryMax - yLocal*imaginaryScale);
      double cImaginary(zImaginary);
      for(int x = 0 ; x < imageWidth ; ++x)            // Iterate over pixels in a row
      {
        zReal = cReal = realMin + x*realScale;
        // Set current pixel color based on n
        SetPixel(memDC, x, 0, Color(IteratePoint(zReal, zImaginary, cReal, cImaginary)));
      }
    };

  for(int y = 1 ; y < imageHeight ; y += 2)             // Iterate over image rows
  {
    Concurrency::parallel_invoke([&]{rowCalc(memDC1, y-1);}, [&]{rowCalc(memDC2, y);});
    // Transfer pixel rows to client area device context
    BitBlt(hdc, 0, y-1, imageWidth, 1, memDC1, 0, 0, SRCCOPY);
    BitBlt(hdc, 0, y, imageWidth, 1, memDC2, 0, 0, SRCCOPY);
  }
  // If there's an odd number of rows, take care of the last one
  if(imageHeight%2 == 1)
  {
    rowCalc(memDC1, imageWidth-1);
    BitBlt(hdc, 0, imageHeight-1, imageWidth, 1, memDC1, 0, 0, SRCCOPY);
  }

  SelectObject(memDC1, oldBmp1);
  SelectObject(memDC2, oldBmp2);
  DeleteObject(bmp1);                       // Delete bitmap 1
  DeleteObject(bmp2);                       // Delete bitmap 2
  DeleteDC(memDC1);                         // and our working DC 1
  DeleteDC(memDC2);                         // and our working DC 2
  ReleaseDC(hWnd, hdc);                     // Release client area DC
}

Данный код выводит тоже самое изображение что и предыдущий код, но он выводит код значительно быстрее чем предыдущий, потому что использует параллельные вычисления библиотеки ppl – алгоритм parallel_invoke.

На этом пожалуй и все, что я хотел написать про пароллельные вычисления. PPL – следует использовать тогда когда время выполнения программы занимает секунды, тогда реально от нее может быть пользя.

Да еще есть задачки решенные по PPL, 3 задачи, вы их мойдете перейдя по ссылке http://www.kselax.ru/2014/08/kniga-visual-c-2010-polnyj-kurs-reshenie-zadach-glava-13/

 

 

 

Комментарии:


Оставить комментарий

Your email address will not be published. Required fields are marked *