摘 要:面對(duì)日益復(fù)雜的智能移動(dòng)應(yīng)用,多核處理器已成為高性能移動(dòng)計(jì)算的一個(gè)有效解決方案。對(duì)于多核系統(tǒng)中的應(yīng)用軟件性能優(yōu)化也是其中的研究重點(diǎn)。本文研究了并行程序設(shè)計(jì)算法和并行程序性能優(yōu)化技術(shù)。通過(guò)對(duì)程序進(jìn)行優(yōu)化,可以使它充分的發(fā)揮多核的計(jì)算能力,其方法包括增加任務(wù)數(shù)量改善負(fù)載均衡,選擇最優(yōu)的線程與處理核之間關(guān)聯(lián)策略,從而能夠大幅提高系統(tǒng)的整體性能。
關(guān)鍵詞:移動(dòng)智能多核系統(tǒng);多線程;并行計(jì)算;分治法;GPA
中圖分類號(hào):TP311.52
由于移動(dòng)智能系統(tǒng)是一個(gè)資源受限的系統(tǒng),它對(duì)程序的運(yùn)行空間和時(shí)間要求比桌面系統(tǒng)更為苛刻,因此,應(yīng)用軟件的優(yōu)化對(duì)移動(dòng)智能系統(tǒng)來(lái)說(shuō)尤顯必要和緊迫[1]。
本文主要研究的性能優(yōu)化是主要指運(yùn)行速度的優(yōu)化。應(yīng)用軟件對(duì)其運(yùn)行速度進(jìn)行優(yōu)化是指在充分掌握軟、硬件特征的基礎(chǔ)上,通過(guò)應(yīng)用程序結(jié)構(gòu)調(diào)整等手段來(lái)縮短完成指定任務(wù)所需的運(yùn)行時(shí)間,主要應(yīng)用在對(duì)實(shí)時(shí)性要求比較高的場(chǎng)合。
目前,移動(dòng)智能系統(tǒng)的處理器在物理上也支持多線程的并發(fā)執(zhí)行,采用適當(dāng)數(shù)量的并發(fā)線程可以獲得比單一線程高的運(yùn)行速度[2]。對(duì)于多核系統(tǒng)中的應(yīng)用軟件性能優(yōu)化,本文研究了基于Android系統(tǒng)的并行程序設(shè)計(jì)算法和并行程序性能優(yōu)化技術(shù)。
1 我們通過(guò)一個(gè)實(shí)際應(yīng)用來(lái)分析、研究基于移動(dòng)平臺(tái)的應(yīng)用軟件優(yōu)化的技術(shù)
包括并行處理技術(shù)、多線程優(yōu)化技術(shù)以及利用GPA(Graphics Performance Analyzers)工具輔助分析技術(shù)。
本例是一個(gè)求圓周率π的應(yīng)用。有如下數(shù)學(xué)公式:
將積分公式表示為極限:
實(shí)際上Δx不可能做到無(wú)窮小,只能讓?duì)盡可能小,這樣求出的結(jié)果越接近π。我們用step表示一個(gè)Δx,則num_step=1/step盡量地大。考慮到f(x)=1/(x2+1)是一個(gè)凸函數(shù),這里取一個(gè)中值來(lái)求和,即使用f[(i+0.5)/(num_steps)]來(lái)代替f[i/(num_steps)]求和,這樣求出的和不會(huì)總是比實(shí)際值偏小。最后可以得出編寫程序依據(jù)的公式為:
根據(jù)上面公式我們編寫出相應(yīng)的計(jì)算程序。
2 原始應(yīng)用(未經(jīng)優(yōu)化)研究與分析
我們首先根據(jù)上述公式直接推導(dǎo)出應(yīng)用的計(jì)算代碼,此代碼是沒(méi)有經(jīng)過(guò)優(yōu)化的,我們稱其為“原始應(yīng)用”,將其命名為SerialPi。
該應(yīng)用設(shè)計(jì)思路是:讓計(jì)算π的任務(wù)放在輔助線程(這里稱為任務(wù)線程)中運(yùn)行,主活動(dòng)上設(shè)置按鈕來(lái)控制線程的運(yùn)行,并用一個(gè)TextView來(lái)顯示任務(wù)線程的結(jié)果。應(yīng)用運(yùn)行的界面如圖1所示。
應(yīng)用啟動(dòng)后的界面如圖1(a)所示。當(dāng)我們點(diǎn)擊“開始計(jì)算”按鈕后,界面所有的按鈕都變成灰色,直到計(jì)算π的線程運(yùn)算完成。這時(shí),界面顯示π的計(jì)算結(jié)果以及線程運(yùn)行的總時(shí)間,如圖1(b)所示。
從上述運(yùn)行界面可知,此應(yīng)用計(jì)算π的時(shí)間大約為22秒多。多次運(yùn)行此應(yīng)用,其顯示的計(jì)算時(shí)間也大約為此時(shí)長(zhǎng)。
圖1 SerialPi應(yīng)用運(yùn)行的界面
2.1 在原始應(yīng)用 中 新 建 線 程 類MyTaskThread,讓其計(jì)算π的值。編輯源文件
MyTaskThread.java,主要代碼如下:
1 package com.example.serialpi;
2 public class MyTaskThread extends Thread {
3 private Handler mainHandler;
4 public static final int MSG_FINISHED = 1; //定義表示\"計(jì)算結(jié)束\"的消息類型
5 private static final long num_steps = 200000000;//公式中的 num_steps 變量,總步數(shù)
6 private static final double step = 1.0 / num_steps;//公式中的step變量,步長(zhǎng)
7 public static double pi = 0.0; //π的計(jì)算結(jié)果
……
20 public void r u n ()
21 {
22 double x ,sum=0.0;
23 long i;
24 for ( i=0; i< num_steps; i++){
25 x = ( i+ 0. 5) * s t e p ;
26 sum=sum+ 4. 0 /(1.0 + x*x );
27 }
28 pI=step*sum;
29 Messagemsg=new Message();
30 msg.what=MSG _FINISHED;//定義消息類型
31 main Hand ler.send Message(msg);//發(fā)送消息}
第22行至第28行是根據(jù)計(jì)算公式書寫的計(jì)算π的代碼。這里x變量是函數(shù)f(x)=1/(x2+1)的自變量x,sum是Σ的累積變量。累積完Σ后,最后在第28行,讓?duì)?step×∑算出最后結(jié)果。【在線程的run函數(shù)中,一旦計(jì)算完成,在第29行開始,就向主線程發(fā)送計(jì)算完成的消息】
2.2 編輯主程序的源代碼文件MainActivity.java,讓其控制線程的運(yùn)行,并顯示計(jì)算結(jié)果,其主要代碼如下:
1 package com.example.serialpi;
2 public class MainActivity extends Activity {
……
13 private MyTaskThread myThread = 1;
14 private TextView tv_TaskOutputInfo;// 顯示(計(jì)算)任務(wù)線程輸出
15 private Handler mHandler;;
16 private long end_time;
17 private long time;
18 private long start_time;
……
38mHandler = new Handler() {
39 public void handleMessage(Message msg) {
40 switch (msg.what)
41 {
42 case MyTaskThread.MSG_FINISHED:
43 end_time = System.currentTimeMillis();
44 time = end_time - start_time;
45 String s = \"運(yùn)行結(jié)束,Pi=\"+ MyTaskThread.pi+ \" 耗時(shí):\"
46 + MyTaskThread.msTimeToDatetime(time);
47 tv_TaskOutputInfo.setText(s);
48 btn_ExitApp.setEnabled(true);
49 break;
50 default:break; } } };
……
61 private void startTask() {
62 myThread = new MyTaskThread(mHandler); //創(chuàng)建一個(gè)線程
63if (! myThread.isAlive())
64{
65 start_time = System.currentTimeMillis();
66 myThread.start(); // 啟動(dòng)線程 } }
我們首先在16行-第18行定義了三個(gè)變量,任務(wù)的開始時(shí)間start_time,任務(wù)的結(jié)束時(shí)間end_time,以及任務(wù)的運(yùn)行時(shí)長(zhǎng)time。它們存在如下關(guān)系:time=end_time-start_time。
在第65行,我們?cè)趩?dòng)任務(wù)線程的同時(shí),將機(jī)器的當(dāng)前時(shí)間記錄在start_time中。在第43行~第44行,當(dāng)收到任務(wù)線程運(yùn)行完畢的消息后,將機(jī)器的當(dāng)前時(shí)間記錄在end_time中,兩者相減得到任務(wù)的運(yùn)行時(shí)長(zhǎng)。
2.3 使用GPA工具來(lái)驗(yàn)證與評(píng)估原始應(yīng)用的性能。上述工作完成后,我們將應(yīng)用部署到實(shí)際的智能設(shè)備上進(jìn)行測(cè)試。本例我們主要監(jiān)控分析兩個(gè)CPU的負(fù)載(即“CPU XX Load”指標(biāo))情況。在監(jiān)控期間,我們點(diǎn)擊“開始計(jì)算”按鈕開始運(yùn)行計(jì)算,記錄下GPA的監(jiān)控信息,分析的結(jié)果如圖2所示。
(a)點(diǎn)擊“開始計(jì)算”按鈕后
(b)線程計(jì)算運(yùn)行中 (c)線程計(jì)算結(jié)束
圖2 原始應(yīng)用(SerialPi)GPA截圖
圖2(a)是點(diǎn)擊“開始計(jì)算”按鈕,計(jì)算(任務(wù))線程運(yùn)行開始時(shí)的顯示。圖2(b)是計(jì)算(任務(wù))線程運(yùn)行中的顯示。圖2(c)是計(jì)算(任務(wù))線程運(yùn)行結(jié)束時(shí)的顯示。從圖中可知,計(jì)算線程沒(méi)有運(yùn)行或運(yùn)行結(jié)束后,CPU的負(fù)載停留在較低水平。而一旦計(jì)算線程運(yùn)行,CPU的負(fù)載急劇上升到100%的滿負(fù)荷水平。但是,在計(jì)算線程運(yùn)行時(shí),兩個(gè)CPU中始終只有一個(gè)CPU是滿負(fù)荷的,另一個(gè)處于低負(fù)載水平,也就是說(shuō)總的負(fù)載率(即負(fù)載之和)只有一個(gè)CPU處于滿負(fù)荷狀態(tài)。
3 對(duì)原始應(yīng)用的性能優(yōu)化
上面的例子,是從計(jì)算π的公式直接想到的代碼。這個(gè)例子有沒(méi)有可能實(shí)現(xiàn)優(yōu)化呢?怎么去優(yōu)化呢?在此,我們對(duì)算法進(jìn)行改進(jìn),充分利用多核處理器的硬件特點(diǎn)來(lái)進(jìn)行優(yōu)化,挖掘出多核處理器的性能潛力。
多核處理器擁有多核、多線程技術(shù),支持多線程在物理上的并行運(yùn)行。例如,對(duì)于上例的運(yùn)行環(huán)境,這個(gè)多核處理器就可以支持兩個(gè)線程的并行運(yùn)行。這就是我們算法優(yōu)化的切入點(diǎn):我們采用“分治法”的思路,設(shè)法讓計(jì)算任務(wù)分?jǐn)偟蕉鄠€(gè)(本例是兩個(gè))線程中來(lái)運(yùn)行,這樣并行運(yùn)行的線程就可以加快運(yùn)行速度。
首先,我們分析一下以上例子的MyTaskThread類的run函數(shù)代碼。為了計(jì)算積分面積的累計(jì)值,我們?cè)诘?4行讓計(jì)算步每次累計(jì)一步來(lái)計(jì)算。實(shí)際上,我們將積分累計(jì)區(qū)域分成“塊”,每個(gè)線程負(fù)責(zé)計(jì)算其中的一塊,最后將每個(gè)線程計(jì)算的累計(jì)面積匯總起來(lái),這樣既可以得到最后的結(jié)果,又能“分而治之”完成任務(wù)的分派執(zhí)行。這就是我們實(shí)現(xiàn)算法優(yōu)化的思路。
我們把優(yōu)化后的應(yīng)用命名為ThreadPi。該應(yīng)用在計(jì)算積分面積的累計(jì)值時(shí),讓每個(gè)線程的計(jì)算步每次累計(jì)步長(zhǎng)增加總線程數(shù),這樣讓每個(gè)線程負(fù)責(zé)累計(jì)自己負(fù)責(zé)的條帶的面積。該應(yīng)用運(yùn)行的界面如圖3所示。
圖3 ThreadPi應(yīng)用運(yùn)行的界面
此應(yīng)用運(yùn)行的界面與原始應(yīng)用一致。從上述運(yùn)行界面可知,此應(yīng)用計(jì)算π的時(shí)間大約為13秒多。其時(shí)間幾乎縮短到原始應(yīng)用的一半。與原始應(yīng)用不一樣的是,本應(yīng)用使用兩個(gè)線程來(lái)計(jì)算π。
3.1 本應(yīng)用是在修改原始應(yīng)用代碼的基礎(chǔ)上得到的,其關(guān)鍵的修改如下。修改計(jì)算任務(wù)線程類MyTaskThread的源代碼文件如下:
1 package com.example.threadpi;
……
4 public class MyTaskThread extends Thread{
5 private Handler mainHandler;
6 public static final int MSG_FINISHED = 1; // 定義表示\"計(jì)算結(jié)束\"的消息類型
7 private static final long num_steps = 200000000;//公式中的 num_steps 變量,總步數(shù)
8 private static final double step = 1.0 / num_steps;//公式中的step變量,步長(zhǎng)
9 public static double pi = 0.0; //π的計(jì)算結(jié)果
10 public static final int num_threads = 2; //線程數(shù)
11private int myNum; //線程號(hào)
12private static Object sharedVariable = new Object();//對(duì)pi變量的同步鎖變量
13private static int finishedThreadNum = 0; //完成計(jì)算的線程數(shù)
……
28 public void run()
29 {
30double x, partialSum = 0.0;
31 long i;
32for (i = myNum; i < num_steps; i += num_threads) {
33 x = (i + 0.5) * step;
34 partialSum += 4.0 / (1.0 + x * x);
35 }
36 synchronized (sharedVariable) {
37 pi+=partialSum * step;
38 finishedThreadNum++;
39 if (finishedThreadNum >= num_threads){//等全部線程運(yùn)行結(jié)束發(fā)送消息
40 Message msg = new Message();
41 msg.what = MSG_FINISHED;//定義消息類型
42 mainHandler.sendMessage(msg); //發(fā)送消息
43 }}}
上述代碼中陰影標(biāo)注文字是本應(yīng)用與原始應(yīng)用主要區(qū)別的地方。在第10行-第13行我們定義了多線程計(jì)算任務(wù)所需的變量。num_threads是計(jì)算任務(wù)開辟的線程數(shù)。
本例運(yùn)行的設(shè)備為兩個(gè)邏輯CPU,故將此值設(shè)為2。myNum是計(jì)算線程的編號(hào),它從0到num_threads-1范圍內(nèi)選擇。sharedVariable變量是對(duì)pi變量施加同步鎖而引入的變量。finishedThreadNum是完成計(jì)算的線程數(shù),但是該值等于num_threads就認(rèn)為所有的計(jì)算線程都已運(yùn)行結(jié)束。
第30行-第43行是計(jì)算線程的原型代碼。其中第30行-第35行是計(jì)算π的直接代碼。對(duì)于原始應(yīng)用的相應(yīng)代碼可知,原始應(yīng)用的sum變量被partialSum所代替,它表示本線程計(jì)算的和(面積)只是全部和(面積)的一部分。最重要的區(qū)別是第32行,這里步長(zhǎng)變量i累加的不是1,而是num_threads,即每一次都是向前移動(dòng)線程數(shù)步。而i起始位置也不是原始應(yīng)用的從0開始,而是從線程號(hào)開始。
當(dāng)線程計(jì)算完自己的和之后,需要將此數(shù)據(jù)放到總的和(pi變量)中去。這是一個(gè)多線程共享的變量,因此需要加同步鎖。這一步驟對(duì)應(yīng)第36行~第44行。第36行加同步鎖,第37行將線程自己的計(jì)算結(jié)果加到公共結(jié)果pi中去。第38行將計(jì)算結(jié)束的線程數(shù)加一。第39行,通過(guò)比較完成計(jì)算的線程數(shù)與總的線程數(shù)來(lái)判斷全部計(jì)算線程是否都已結(jié)束,只有都結(jié)束了,才向主線程發(fā)送計(jì)算結(jié)束的消息。
3.2 修改主程序MainActivity的源代碼文件MainActivity.java如下:
1 package com.example.threadpi;
……
12 public class MainActivity extends Activity{
13 private MyTaskThread thrd[]=1;
14 private long end_time;
15private long time;
16private long start_time;
……
64 private void startTask() {
65thrd = new MyTaskThread[MyTaskThread.num_threads];
66 start_time = System.currentTimeMillis();
67 for( int i=0; i < MyTaskThread.num_threads;i++){
68 thrd[i] = new MyTaskThread(mHandler); //創(chuàng)建一個(gè)線程
69 thrd[i].setStepStartNum(i);
70 thrd[i].start();
71 } }
73 private void exitApp() {
74 for (int i = 0; i < MyTaskThread.num_threads thrd != 1; i++) {
75 try {
76 thrd[i].join(); //等待線程運(yùn)行結(jié)束
77 } catch (InterruptedException e) {
78 }}
……
上述代碼中陰影標(biāo)注文字是本應(yīng)用與原始應(yīng)用主要區(qū)別的地方。在第13行,原始應(yīng)用的單個(gè)線程對(duì)象變量改為了線程數(shù)組。在啟動(dòng)計(jì)算任務(wù)的第67行~第71行,原始應(yīng)用啟動(dòng)單個(gè)線程,改為啟動(dòng)數(shù)組中的全部線程,并在啟動(dòng)時(shí)設(shè)置線程的編號(hào)。在等待線程結(jié)束的第74行-第78行,原始應(yīng)用中等待單個(gè)線程結(jié)束,改為等待線程數(shù)組中的全部線程結(jié)束。
上述工作完成后,我們同樣將應(yīng)用部署到原始應(yīng)用相同的目標(biāo)機(jī)上。我們單獨(dú)運(yùn)行該應(yīng)用,來(lái)測(cè)試一下優(yōu)化后的應(yīng)用運(yùn)行時(shí)間,結(jié)果是:時(shí)間幾乎縮短到原來(lái)的一半(由原來(lái)的22秒減少到13秒)。
3.3 使用GPA工具來(lái)驗(yàn)證與評(píng)估優(yōu)化的應(yīng)用性能。下面使用GPA來(lái)分析此應(yīng)用,分析過(guò)程與原始應(yīng)用一致,分析的結(jié)果如圖4所示。
(a)點(diǎn)擊“開始計(jì)算”按鈕后
(b)線程計(jì)算運(yùn)行中 (c)線程計(jì)算結(jié)束
圖4 優(yōu)化應(yīng)用(ThreadPi)的GPA截圖
從圖中可以看出,點(diǎn)擊“開始計(jì)算”按鈕,計(jì)算(任務(wù))線程運(yùn)行開始后,兩個(gè)CPU的負(fù)載都從低負(fù)載立即上升到滿負(fù)荷狀態(tài)。計(jì)算完成后,兩個(gè)CPU的負(fù)載都重新回到低負(fù)荷狀態(tài)。與原始應(yīng)用不同是,在計(jì)算任務(wù)運(yùn)行期間,全部的CPU都處于滿負(fù)荷狀態(tài),不存在負(fù)載在兩個(gè)CPU中輪流的情況。這說(shuō)明該應(yīng)用的計(jì)算任務(wù)使兩個(gè)CPU并行工作在滿負(fù)荷狀態(tài),這是優(yōu)化后的應(yīng)用速度提高(本例提高將近100%)的內(nèi)在原因。
4 結(jié)束語(yǔ)
本文主要研究了基于移動(dòng)平臺(tái)的多核系統(tǒng)的應(yīng)用軟件優(yōu)化技術(shù)。其中,多線程、平行計(jì)算技術(shù)是移動(dòng)平臺(tái)系統(tǒng)的研究重點(diǎn)、難點(diǎn),合理正確的使用這些技術(shù),可以讓多個(gè)處理器處于均衡負(fù)載狀態(tài),以提高系統(tǒng)的整體運(yùn)行速度,從而達(dá)到性能優(yōu)化的目的,使我們的應(yīng)用軟件運(yùn)行更加流暢、體驗(yàn)更加好。
今后,我們?cè)谝苿?dòng)智能多核系統(tǒng)實(shí)現(xiàn)上,還有一些方面需要不斷地完善和進(jìn)一步研究的地方,比如:進(jìn)一步完善移動(dòng)智能多核平臺(tái)上的并行編譯,支持更廣泛的并行模式,并且支持更廣泛的移動(dòng)智能多核結(jié)構(gòu)。
參考文獻(xiàn):
[1]Butler,M.Android:Changing the Mobile Landscape.PERVASIVE COMPUTING,IEEE,2011.
[2]Ian K.T.Tan,Ian Chai,Poo Kuan Hoong.Dynamic threshold for imbalance assessment on load balancing for multicore systems[J].Computers and Electrical Engineering,2012.
[3]R.Rakvic,Q.Cai,J.González,G.Magklis,P.Chaparro,A.González.Thread-management techniques to maximize efficiency in multicore and simultaneous multithreaded microprocessors[J].ACM Transactions on Architecture and Code Optimization(TACO),2010(02).
[4]史莉雯,樊曉婭,張盛兵.單片多處理器的研究[J].計(jì)算機(jī)應(yīng)用研究,2007(09):46-49.
[5]李濤,高德遠(yuǎn),樊曉婭.高性能微處理器性能模型設(shè)計(jì)[J].航空電子技術(shù)2011(02):25-28.
[6]ShahidBokhari,JoelSaltz.Exploring the performance of massively multithreaded architectures[J].Concurrency Computat:Pract.Exper,2010(05).
[7]Stijn Eyerman,Lieven Eeckhout.Modeling critical sections in Amdahl’s law and its implications for multicore design[J].ACM SIGARCH Computer Architecture News,2010(03).
作者簡(jiǎn)介:何偉文(1970-),男,廣東廣州人,碩士,高級(jí)工程師,研究方向:網(wǎng)絡(luò)安全,系統(tǒng)工程,項(xiàng)目管理。
作者單位:廣州涉外經(jīng)濟(jì)職業(yè)技術(shù)學(xué)院 信息學(xué)院,廣州 510540