В этой статье мы разберем, что такое параллельное программирование и как может программа параллельно выполняться. Я давно хотел эту тему поднять, но все никак она мне не попадалась в книгах, а так разбирать не охота было, а сейчас дошел я до этой темы и так мы будем рассматривать библиотеку 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 параллельно выполняет набор из двух или нескольких независимых задач.
Рассмотрим как используется каждый алгоритм.
Разсмотрим пример
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#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 можно добавлять шаг, тоесть избирать элементы для которых будет рассчитываться куб, например для каждого третьего элемента.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
#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; } |
вывод будет уже другой чуток:
Вывод уже другой как видно из рисунка выше. Мы просто добавили еще один параметр шаг и вызвали перегруженную функцию с шагом.
Рассмотрим следующий алгоритм parallel_for_each
Алгоритм parallel_for_each подобен алгоритму parallel_for, но отличия в том, что данная функция работает с контейнерами STL и принимает в качестве параметров итераторы. От простой примерчик использования parallel_for_each.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
#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 можно вызвать исключение и обработать
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
#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; } |
Вывод:
В коде выше алгоритм parallel_for_each меняет местами имена и фамилии в каждом элементе массива mass, используя параллельные операции. Вывод, созданный алгоритмом STL for_each, демонстрирует, что все работает так, как нада.
Затем мы создаем искомую строку name. Переменная a инициализируется значением, возвращенным функцией findIndex(). Оператор if выводит сообщение в зависимости от значения переменной a, и последняя строка вывода демонстрирует что значение переменной name действительно было найдено в массиве mass.
Использование алгоритма parallel_invoke
Алгоритм parallel_invoke определяется шаблоном, который позволяет решать параллельно до десяти задачь. Вполне очевидно, что нет никакого смысла пытаться решать параллельно больше задач, чем есть процессоров на вашем компьютере. Аргументы функции parallel_invoke — это объекты функции, определяющие задачи, которые будут решаться параллельно. Рассмотрим пример.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#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; } |
вывод:
В примере выше выполняются две функции параллельно, одна из них подсчитывает куб, а вторая квадрат числа.
Приведенные здесь примеры и фрагменты кода не были теми вычислениями, которые могли бы извлечь реальную пользу от параллельного выполнения.
combinable предназначен для помощи для помощи в совместном использовании ресурсов, которые могут быть сегментированы в локальные переменные для потоков и объединены после завершения вычисления.
Разсмотрим пример использования combinable для подсчета количества суммы элементов в массиве.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#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; } |
вывод:
в коде выше все ясно, что там происходит. Создается класс combinable<double> sums;, затем в лямбда выражении в строчке sums.local()+=mass[i]; создаются две локальные суммы, как бы они параллельно создаются. А дальше в строке
1 2 3 4 5 |
double sum=sums.combine( [](double s1,double s2)->double { return s1+s2; }); |
из двух локальных переменных сумма записывается в переменную doulbe sum.
Вы можете использовать объект task_group для параллельного решения двух и более задач, где задача может быть определена функтором или лямбда-выражением. Объект task_group потокобезопасен, таким образом, можете использовать его для инициализации задачи для разных потоков.
Рассмотрим пример кода в котором параллельно подсчитывается сумма в двух массивах с использованием объекта task_group.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#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; } |
вывод:
в коде есть подробные комментарии подробно описывающие как работает код. В функции run можно определить несколько лямбда — выражений, в данном примере мы определили одно лямбда выражение, и оно выполняется отдельным процессом, идущий следом цикл for выполняется уже в том процессе в котором выполняется сама программа.
Данный объект может использоваться только для инициализации задачи одного потока. В отличие от объектов task_group вы не сможете передать лямбда-выражение или объект функции в функцию run() для объекта structured_task_group.
Извините но пример который был в книге у меня почему то не заработал, поэтому я пока этот объект пропустю.
Отображение набора Мандельброта
И так рассмотрим отображение набора Мандельброта, от есть код на winapi с помощью которого выводится окно с набором Мандельброта.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
// 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(), без замены исходной. Сохраним обе версии в исходном коде примера
код
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 |
// 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/