日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

用OpenMP加速你的程序[转]

發(fā)布時(shí)間:2023/12/10 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 用OpenMP加速你的程序[转] 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

????? 最近在看多核編程。簡單來說,由于現(xiàn)在電腦CPU一般都有兩個(gè)核,4核與8核的CPU也逐漸走入了尋常百姓家,傳統(tǒng)的單線程編程方式難以發(fā)揮多核 CPU的強(qiáng)大功能,于是多核編程應(yīng)運(yùn)而生。按照我的理解,多核編程可以認(rèn)為是對多線程編程做了一定程度的抽象,提供一些簡單的API,使得用戶不必花費(fèi)太多精力來了解多線程的底層知識(shí),從而提高編程效率。這兩天關(guān)注的多核編程的工具包括openMP和TBB。按照目前網(wǎng)上的討論,TBB風(fēng)頭要蓋過 openMP,比如openCV過去是使用openMP的,但從2.3版本開始拋棄openMP,轉(zhuǎn)向TBB。但我試下來,TBB還是比較復(fù)雜的,相比之下,openMP則非常容易上手。因?yàn)榫蜁r(shí)間有限,沒辦法花費(fèi)太多時(shí)間去學(xué)習(xí)TBB,就在這里分享下這兩天學(xué)到的openMP的一點(diǎn)知識(shí),首先從openMP的基本知識(shí)入手,最后針對圖像特征點(diǎn)提取給出一個(gè)結(jié)合openCV的具體的例子。
openMP支持的編程語言包括C語言、C++和Fortran,支持OpenMP的編譯器包括Sun Studio,Intel Compiler,Microsoft Visual Studio,GCC。我使用的是Microsoft Visual Studio 2008,CPU為Intel i5 四核,首先講一下在Microsoft Visual Studio 2008上openMP的配置。非常簡單,總共分2步:
(1) 新建一個(gè)工程。這個(gè)不再多講。
(2) 建立工程后,點(diǎn)擊 菜單欄->Project->Properties,彈出菜單里,點(diǎn)擊 Configuration Properties->C/C++->Language->OpenMP Support,在下拉菜單里選擇Yes。
至此配置結(jié)束。下面我們通過一個(gè)小例子來說明openMP的易用性。這個(gè)例子是 有一個(gè)簡單的test()函數(shù),然后在main()里,用一個(gè)for循環(huán)把這個(gè)test()函數(shù)跑8遍。

#include <iostream> #include <time.h> void test() {int a = 0;for (int i=0;i<100000000;i++)a++; } int main() {clock_t t1 = clock();for (int i=0;i<8;i++)test();clock_t t2 = clock();std::cout<<"time: "<<t2-t1<<std::endl; }


編譯運(yùn)行后,打印出來的耗時(shí)為:1.971秒。下面我們用一句話把上面代碼變成多核運(yùn)行。

#include <iostream> #include <time.h> void test() {int a = 0;for (int i=0;i<100000000;i++)a++; } int main() {clock_t t1 = clock();#pragma omp parallel forfor (int i=0;i<8;i++)test();clock_t t2 = clock();std::cout<<"time: "<<t2-t1<<std::endl; }


編譯運(yùn)行后,打印出來的耗時(shí)為:0.546秒,幾乎為上面時(shí)間的1/4。
由此我們可以看到openMP的簡單易用。在上面的代碼里,我們一沒有額外include頭文件,二沒有額外link庫文件,只是在for循環(huán)前加了一句#pragma omp parallel for。而且這段代碼在單核機(jī)器上,或者編譯器沒有將openMP設(shè)為Yes的機(jī)器上編譯也不會(huì)報(bào)錯(cuò),將自動(dòng)忽略#pragma這行代碼,然后按照傳統(tǒng)單核串行的方式編譯運(yùn)行!我們唯一要多做的一步,是從C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\x86\Microsoft.VC90.OPENMP和C:\Program Files\Microsoft Visual Studio 9.0\VC\redist\Debug_NonRedist\x86\Microsoft.VC90.DebugOpenMP目錄下分別拷貝 vcomp90d.dll和vcomp90.dll文件到工程文件當(dāng)前目錄下。
對上面代碼按照我的理解做個(gè)簡單的剖析。
當(dāng)編譯器發(fā)現(xiàn)#pragma omp parallel for后,自動(dòng)將下面的for循環(huán)分成N份,(N為電腦CPU核數(shù)),然后把每份指派給一個(gè)核去執(zhí)行,而且多核之間為并行執(zhí)行。下面的代碼驗(yàn)證了這種分析。

#include <iostream> int main() { #pragma omp parallel forfor (int i=0;i<10;i++)std::cout<<i<<std::endl;return 0; }

會(huì)發(fā)現(xiàn)控制臺(tái)打印出了0 3 4 5 8 9 6 7 1 2。注意:因?yàn)槊總€(gè)核之間是并行執(zhí)行,所以每次執(zhí)行時(shí)打印出的順序可能都是不一樣的。
下面我們來了談?wù)劯倯B(tài)條件(race condition)的問題,這是所有多線程編程最棘手的問題。該問題可表述為,當(dāng)多個(gè)線程并行執(zhí)行時(shí),有可能多個(gè)線程同時(shí)對某變量進(jìn)行了讀寫操作,從而導(dǎo)致不可預(yù)知的結(jié)果。比如下面的例子,對于包含10個(gè)整型元素的數(shù)組a,我們用for循環(huán)求它各元素之和,并將結(jié)果保存在變量sum里。

#include <iostream> int main() {int sum = 0;int a[10] = {1,2,3,4,5,6,7,8,9,10}; #pragma omp parallel forfor (int i=0;i<10;i++)sum = sum + a[i];std::cout<<"sum: "<<sum<<std::endl;return 0; }

如果我們注釋掉#pragma omp parallel for,讓程序先按照傳統(tǒng)串行的方式執(zhí)行,很明顯,sum = 55。但按照并行方式執(zhí)行后,sum則會(huì)變成其他值,比如在某次運(yùn)行過程中,sum = 49。其原因是,當(dāng)某線程A執(zhí)行sum = sum + a[i]的同時(shí),另一線程B正好在更新sum,而此時(shí)A還在用舊的sum做累加,于是出現(xiàn)了錯(cuò)誤。
那么用openMP怎么實(shí)現(xiàn)并行數(shù)組求和呢?下面我們先給出一個(gè)基本的解決方案。該方案的思想是,首先生成一個(gè)數(shù)組sumArray,其長度為并行執(zhí)行的線程的個(gè)數(shù)(默認(rèn)情況下,該個(gè)數(shù)等于CPU的核數(shù)),在for循環(huán)里,讓各個(gè)線程更新自己線程對應(yīng)的sumArray里的元素,最后再將 sumArray里的元素累加到sum里,代碼如下

#include <iostream> #include <omp.h> int main(){int sum = 0;int a[10] = {1,2,3,4,5,6,7,8,9,10};int coreNum = omp_get_num_procs();//獲得處理器個(gè)數(shù)int* sumArray = new int[coreNum];//對應(yīng)處理器個(gè)數(shù),先生成一個(gè)數(shù)組for (int i=0;i<coreNum;i++)//將數(shù)組各元素初始化為0sumArray[i] = 0; #pragma omp parallel forfor (int i=0;i<10;i++){int k = omp_get_thread_num();//獲得每個(gè)線程的IDsumArray[k] = sumArray[k]+a[i];}for (int i = 0;i<coreNum;i++)sum = sum + sumArray[i];std::cout<<"sum: "<<sum<<std::endl;return 0; }

需要注意的是,在上面代碼里,我們用omp_get_num_procs()函數(shù)來獲取處理器個(gè)數(shù),用omp_get_thread_num()函數(shù)來獲得每個(gè)線程的ID,為了使用這兩個(gè)函數(shù),我們需要include <omp.h>。
上面的代碼雖然達(dá)到了目的,但它產(chǎn)生了較多的額外操作,比如要先生成數(shù)組sumArray,最后還要用一個(gè)for循環(huán)將它的各元素累加起來,有沒有更簡便的方式呢?答案是有,openMP為我們提供了另一個(gè)工具,歸約(reduction),見下面代碼:

#include <iostream> int main(){int sum = 0;int a[10] = {1,2,3,4,5,6,7,8,9,10}; #pragma omp parallel for reduction(+:sum)for (int i=0;i<10;i++)sum = sum + a[i];std::cout<<"sum: "<<sum<<std::endl;return 0; }

上面代碼里,我們在#pragma omp parallel for 后面加上了 reduction(+:sum),它的意思是告訴編譯器:下面的for循環(huán)你要分成多個(gè)線程跑,但每個(gè)線程都要保存變量sum的拷貝,循環(huán)結(jié)束后,所有線程把自己的sum累加起來作為最后的輸出。
reduction雖然很方便,但它只支持一些基本操作,比如+,-,*,&,|,&&,||等。有些情況下,我們既要避免race condition,但涉及到的操作又超出了reduction的能力范圍,應(yīng)該怎么辦呢?這就要用到openMP的另一個(gè)工具,critical。來看下面的例子,該例中我們求數(shù)組a的最大值,將結(jié)果保存在max里。

#include <iostream> int main(){int max = 0;int a[10] = {11,2,33,49,113,20,321,250,689,16}; #pragma omp parallel forfor (int i=0;i<10;i++){int temp = a[i]; #pragma omp critical{if (temp > max)max = temp;}}std::cout<<"max: "<<max<<std::endl;return 0; }

上例中,for循環(huán)還是被自動(dòng)分成N份來并行執(zhí)行,但我們用#pragma omp critical將 if (temp > max) max = temp 括了起來,它的意思是:各個(gè)線程還是并行執(zhí)行for里面的語句,但當(dāng)你們執(zhí)行到critical里面時(shí),要注意有沒有其他線程正在里面執(zhí)行,如果有的話,要等其他線程執(zhí)行完再進(jìn)去執(zhí)行。這樣就避免了race condition問題,但顯而易見,它的執(zhí)行速度會(huì)變低,因?yàn)榭赡艽嬖诰€程等待的情況。
有了以上基本知識(shí),對我來說做很多事情都足夠了。下面我們來看一個(gè)具體的應(yīng)用例,從硬盤讀入兩幅圖像,對這兩幅圖像分別提取特征點(diǎn),特征點(diǎn)匹配,最后將圖像與匹配特征點(diǎn)畫出來。編譯該例需要opencv,我用的版本是2.3.1,關(guān)于 opencv的安裝與配置不在此介紹。我們首先來看傳統(tǒng)串行編程的方式。

#include "opencv2/highgui/highgui.hpp" #include "opencv2/features2d/features2d.hpp" #include <iostream> #include <omp.h> int main( ){cv::SurfFeatureDetector detector( 400 ); cv::SurfDescriptorExtractor extractor;cv::BruteForceMatcher<cv::L2<float> > matcher;std::vector< cv::DMatch > matches;cv::Mat im0,im1;std::vector<cv::KeyPoint> keypoints0,keypoints1;cv::Mat descriptors0, descriptors1;double t1 = omp_get_wtime( );//先處理第一幅圖像im0 = cv::imread("rgb0.jpg", CV_LOAD_IMAGE_GRAYSCALE );detector.detect( im0, keypoints0);extractor.compute( im0,keypoints0,descriptors0);std::cout<<"find "<<keypoints0.size()<<"keypoints in im0"<<std::endl;//再處理第二幅圖像im1 = cv::imread("rgb1.jpg", CV_LOAD_IMAGE_GRAYSCALE );detector.detect( im1, keypoints1);extractor.compute( im1,keypoints1,descriptors1);std::cout<<"find "<<keypoints1.size()<<"keypoints in im1"<<std::endl;double t2 = omp_get_wtime( );std::cout<<"time: "<<t2-t1<<std::endl;matcher.match( descriptors0, descriptors1, matches );cv::Mat img_matches;cv::drawMatches( im0, keypoints0, im1, keypoints1, matches, img_matches ); cv::namedWindow("Matches",CV_WINDOW_AUTOSIZE);cv::imshow( "Matches", img_matches );cv::waitKey(0);return 1; }

很明顯,讀入圖像,提取特征點(diǎn)與特征描述子這部分可以改為并行執(zhí)行,修改如下:

#include "opencv2/highgui/highgui.hpp" #include "opencv2/features2d/features2d.hpp" #include <iostream> #include <vector> #include <omp.h> int main( ){int imNum = 2;std::vector<cv::Mat> imVec(imNum);std::vector<std::vector<cv::KeyPoint>>keypointVec(imNum);std::vector<cv::Mat> descriptorsVec(imNum);cv::SurfFeatureDetector detector( 400 ); cv::SurfDescriptorExtractor extractor;cv::BruteForceMatcher<cv::L2<float> > matcher;std::vector< cv::DMatch > matches;char filename[100];double t1 = omp_get_wtime( ); #pragma omp parallel forfor (int i=0;i<imNum;i++){sprintf(filename,"rgb%d.jpg",i);imVec[i] = cv::imread( filename, CV_LOAD_IMAGE_GRAYSCALE );detector.detect( imVec[i], keypointVec[i] );extractor.compute( imVec[i],keypointVec[i],descriptorsVec[i]);std::cout<<"find "<<keypointVec[i].size()<<"keypoints in im"<<i<<std::endl;}double t2 = omp_get_wtime( );std::cout<<"time: "<<t2-t1<<std::endl;matcher.match( descriptorsVec[0], descriptorsVec[1], matches );cv::Mat img_matches;cv::drawMatches( imVec[0], keypointVec[0], imVec[1], keypointVec[1], matches, img_matches ); cv::namedWindow("Matches",CV_WINDOW_AUTOSIZE);cv::imshow( "Matches", img_matches );cv::waitKey(0);return 1; }

兩種執(zhí)行方式做比較,時(shí)間為:2.343秒v.s. 1.2441秒
在上面代碼中,為了改成適合#pragma omp parallel for執(zhí)行的方式,我們用了STL的vector來分別存放兩幅圖像、特征點(diǎn)與特征描述子,但在某些情況下,變量可能不適合放在vector里,此時(shí)應(yīng)該怎么辦呢?這就要用到openMP的另一個(gè)工具,section,代碼如下:

#include "opencv2/highgui/highgui.hpp" #include "opencv2/features2d/features2d.hpp" #include <iostream> #include <omp.h> int main( ){cv::SurfFeatureDetector detector( 400 ); cv::SurfDescriptorExtractor extractor;cv::BruteForceMatcher<cv::L2<float> > matcher;std::vector< cv::DMatch > matches;cv::Mat im0,im1;std::vector<cv::KeyPoint> keypoints0,keypoints1;cv::Mat descriptors0, descriptors1;double t1 = omp_get_wtime( ); #pragma omp parallel sections{ #pragma omp section{std::cout<<"processing im0"<<std::endl;im0 = cv::imread("rgb0.jpg", CV_LOAD_IMAGE_GRAYSCALE );detector.detect( im0, keypoints0);extractor.compute( im0,keypoints0,descriptors0);std::cout<<"find "<<keypoints0.size()<<"keypoints in im0"<<std::endl;} #pragma omp section{std::cout<<"processing im1"<<std::endl;im1 = cv::imread("rgb1.jpg", CV_LOAD_IMAGE_GRAYSCALE );detector.detect( im1, keypoints1);extractor.compute( im1,keypoints1,descriptors1);std::cout<<"find "<<keypoints1.size()<<"keypoints in im1"<<std::endl;}}double t2 = omp_get_wtime( );std::cout<<"time: "<<t2-t1<<std::endl;matcher.match( descriptors0, descriptors1, matches );cv::Mat img_matches;cv::drawMatches( im0, keypoints0, im1, keypoints1, matches, img_matches ); cv::namedWindow("Matches",CV_WINDOW_AUTOSIZE);cv::imshow( "Matches", img_matches );cv::waitKey(0);return 1; }

上面代碼中,我們首先用#pragma omp parallel sections將要并行執(zhí)行的內(nèi)容括起來,在它里面,用了兩個(gè)#pragma omp section,每個(gè)里面執(zhí)行了圖像讀取、特征點(diǎn)與特征描述子提取。將其簡化為偽代碼形式即為:

#pragma omp parallel sections {#pragma omp section{function1();}#pragma omp section{function2();} }

意思是:parallel sections里面的內(nèi)容要并行執(zhí)行,具體分工上,每個(gè)線程執(zhí)行其中的一個(gè)section,如果section數(shù)大于線程數(shù),那么就等某線程執(zhí)行完它的 section后,再繼續(xù)執(zhí)行剩下的section。在時(shí)間上,這種方式與人為用vector構(gòu)造for循環(huán)的方式差不多,但無疑該種方式更方便,而且在單核機(jī)器上或沒有開啟openMP的編譯器上,該種方式不需任何改動(dòng)即可正確編譯,并按照單核串行方式執(zhí)行。
以上分享了這兩天關(guān)于openMP的一點(diǎn)學(xué)習(xí)體會(huì),其中難免有錯(cuò)誤,歡迎指正。另外的一點(diǎn)疑問是,看到各種openMP教程里經(jīng)常用到 private,shared等來修飾變量,這些修飾符的意義和作用我大致明白,但在我上面所有例子中,不加這些修飾符似乎并不影響運(yùn)行結(jié)果,不知道這里面有哪些講究。
在寫上文的過程中,參考了包括以下兩個(gè)網(wǎng)址在內(nèi)的多個(gè)地方的資源,不再一 一列出,在此一并表示感謝。
http://blog.csdn.net/drzhouweiming/arti ... ls/4093624
http://software.intel.com/zh-cn/article ... ith-openmp
原文發(fā)表于我的博客:http://www.cnblogs.com/yangyangcv/archive/2012/03/23/2413335.html,歡迎訪問~~

轉(zhuǎn)載于:https://www.cnblogs.com/CVArt/archive/2012/06/01/2529824.html

總結(jié)

以上是生活随笔為你收集整理的用OpenMP加速你的程序[转]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。