在ARM平臺(tái)上如何才能以節(jié)能方式來(lái)設(shè)計(jì)和編軟件
內(nèi)容摘要:在強(qiáng)調(diào)便攜式設(shè)備的電池壽命時(shí),看起來(lái)比較奇怪是,很少有軟件工程師在其日常項(xiàng)目任務(wù)中真正做到了減少能耗。我猜想對(duì)這個(gè)課題有所想法的那些人可能都是本著榮辱自擔(dān)的想法才關(guān)注此課題的。我們到了要做些改變的時(shí)期。隨著電池壽命和性能要求的矛盾持續(xù),我們作為軟件工程師不得不要用更多時(shí)間來(lái)找出如何才能以節(jié)能方式來(lái)設(shè)計(jì)和編寫(xiě)我們的軟件,至少在工具能滿(mǎn)足我們要求之前需要這樣做。
我們這些軟件工程師都熱衷于為我們遇到的問(wèn)題找出完美的解決方案。但奇怪的是,我們會(huì)發(fā)現(xiàn)在這個(gè)特定的領(lǐng)域,沒(méi)有一個(gè)完美的解決方案。聰明的技巧可能會(huì)節(jié)省一些功率,但是這個(gè)領(lǐng)域是由其他更簡(jiǎn)單的因素支配的。就象房間里有幾頭很大的大象,我們必須要先小心翼翼地獵取我們可以看到的大象,然后再花精力去獵捕體型小的動(dòng)物。
在考量某個(gè)系統(tǒng)的功耗時(shí),重要的是要弄清楚我們實(shí)際測(cè)量的對(duì)象是什么。我們說(shuō)的“節(jié)省功耗”可能意味著幾個(gè)方面。它意味著“功率”還是“能量”。實(shí)際上,我們既需要功率也需要能量。大多數(shù)手持便攜式設(shè)備均具有以下兩個(gè)不同的預(yù)算:功率預(yù)算——它管理著瞬間功耗、避免過(guò)熱或產(chǎn)生熱應(yīng)力,而能量預(yù)算則管理著長(zhǎng)期使用的能量總數(shù)。而軟件則需要滿(mǎn)足短期的功率預(yù)算和長(zhǎng)期的能量預(yù)算。
很明顯,我們可以把任何設(shè)備的功耗降低到接近零,只要不讓它做任何操作或任何有意義的操作即可!不得已的是,實(shí)現(xiàn)有用的功能就需要耗費(fèi)能量。因此,我們只能在有意義的操作和節(jié)能二者中不斷采取折衷方案。為了實(shí)現(xiàn)所需的功能,我們必須耗能;但我們必須盡量確保以節(jié)能的方式來(lái)實(shí)現(xiàn)這些功能。
功耗時(shí)間積 有關(guān)該主題的學(xué)術(shù)材料中常用的更好度量方式是采用“功耗時(shí)間積”。盡管既沒(méi)有標(biāo)準(zhǔn)單位也沒(méi)有具體方法,但是這種度量方式將能耗和性能度量結(jié)合到了一起。增加能耗或降低性能會(huì)增加功耗時(shí)間積的值,因此我們的目標(biāo)是找出最低的可接受的功耗時(shí)間積的值,換句話說(shuō),最低的能耗要與允許的時(shí)間內(nèi)所需任務(wù)的執(zhí)行保持一致。
能量去哪兒了? 所有計(jì)算器械均會(huì)執(zhí)行兩個(gè)基本功能。這兩個(gè)功能都是必需的,沒(méi)有這兩個(gè)功能就不能完成任何有意義的任務(wù)。
我們首先想到的自然是計(jì)算或數(shù)據(jù)處理。通常,計(jì)算是對(duì)機(jī)器寄存器中保存的值實(shí)施的操作。為了盡可能高效地實(shí)施計(jì)算任務(wù),我們需要在最短的時(shí)間內(nèi)執(zhí)行最少的指令。最重要的是,高效計(jì)算允許以下二選一:要么我們可以早點(diǎn)完成計(jì)算去睡覺(jué),要么我們調(diào)慢時(shí)鐘且仍然在規(guī)定時(shí)間內(nèi)完成計(jì)算任務(wù)。
這里經(jīng)常被人忽視的是數(shù)據(jù)通信(數(shù)據(jù)移動(dòng))。在大多數(shù)架構(gòu)中(A R M采用加載/存儲(chǔ)架構(gòu),也不例外),數(shù)據(jù)移動(dòng)是必需的。如果不將信息從一個(gè)位置移動(dòng)到另一個(gè)位置且經(jīng)常返回原來(lái)的位置,用戶(hù)就無(wú)法處理任何信息。例如,內(nèi)存中的值需要移動(dòng)到寄存器中進(jìn)行處理,然后把結(jié)果寫(xiě)回到內(nèi)存中。
但是哪個(gè)耗用的能量更多呢?最大的償付在哪兒?
圖1顯示了普遍存在的事實(shí),與程序有關(guān)的內(nèi)存存取操作中有大約60%是指令抓取,另外40%才是數(shù)據(jù)存取。
圖1:內(nèi)存存取分布
圖2:內(nèi)存存取能耗
圖2顯示了A R M進(jìn)行的一些研究。如果執(zhí)行一條指令的能耗是1,那么,緊耦合存儲(chǔ)器(TCM)存取的能耗約為1/25,緩存存取的能耗大約為1/6。而外部R A M存取的能耗則是指令執(zhí)行能耗的7倍。
換言之,對(duì)于每次外部R A M存取所用的能耗,我們可以執(zhí)行7條指令,40次緩存存取或大約170次TCM存取。
計(jì)算廉價(jià)但通信昂貴
因此,似乎數(shù)據(jù)移動(dòng)要比數(shù)據(jù)處理更昂貴。因此,第一頭大象就是數(shù)據(jù)效率。
我們可以為內(nèi)存存取的能耗管理提出兩個(gè)規(guī)則。
近距離 - 從能量角度講,內(nèi)存越靠近核心,訪問(wèn)內(nèi)存的相對(duì)能耗越低。
少存取 - 減少內(nèi)存存取次數(shù)比減少指令數(shù)量更加重要。
充分利用片上存儲(chǔ)器
從我們的能量圖可以清楚地看出,TCM是到目前為止系統(tǒng)具有的最高效存儲(chǔ)器類(lèi)型。不是所有的系統(tǒng)均具有A R M稱(chēng)為T(mén)CM的存儲(chǔ)器(通過(guò)專(zhuān)用和優(yōu)化的接口連接到內(nèi)核),但是大部分系統(tǒng)至少具有某種片上快速存儲(chǔ)器類(lèi)型。為了便于討論,我們指的是常見(jiàn)的片上存儲(chǔ)器(SPM)。假定S PM單次存取能耗大約是外部R A M存取能耗的1/170,充分利用這種SPM存儲(chǔ)器應(yīng)該是首選。
圖3:SPM的能量?jī)?yōu)勢(shì)
圖3中的圖表顯示了簡(jiǎn)單的“多類(lèi)”基準(zhǔn),甚至128個(gè)字節(jié)的S PM區(qū)域都可以減少大約一半的功耗。1k字節(jié)的存儲(chǔ)器最大可減少70%的功耗。本次研究(Mar wedel, 2004)中采用的方法是從外部R AM到S PM動(dòng)態(tài)重定位代碼和數(shù)據(jù)片段。甚至在按需移動(dòng)各項(xiàng)開(kāi)銷(xiāo)方面,不僅降低了能耗,性能也提高了大約60%。
很顯然,我們?cè)谀硞€(gè)點(diǎn)上正在損失回報(bào)。這種情況下,S PM超過(guò)1k時(shí),性能提升幅度變緩,系統(tǒng)總能耗也會(huì)稍微升高。在這里,我們實(shí)際上在承擔(dān)這種特定應(yīng)用無(wú)法使用的S P M能耗,因?yàn)檫@種應(yīng)用程序本身并不夠大。
您還可以注意到,在結(jié)合了所使用的分配算法時(shí),這種特定的應(yīng)用無(wú)法使用小于64字節(jié)的S P M區(qū)域,因?yàn)闆](méi)有足夠小的可用片段與之相配。本次研究中還展示了一個(gè)更加成熟的算法,可以在最佳狀況下節(jié)省能耗可以超過(guò)80%。
永遠(yuǎn)做緩存友好的事
分析緩存優(yōu)點(diǎn)有時(shí)可能比分析S PM優(yōu)點(diǎn)更加復(fù)雜。一方面,緩存基本上是自我管理的。另一方面,緩存不是對(duì)單個(gè)存儲(chǔ)位置進(jìn)行操作,而是對(duì)固定大小的“線路”進(jìn)行操作。因此,訪問(wèn)單個(gè)可緩存的存儲(chǔ)位置可能加載整條線路,從而造成突發(fā)的內(nèi)存存取。如果該附加的數(shù)據(jù)從未被訪問(wèn)過(guò),則所消耗的能量就浪費(fèi)了。
另一個(gè)不利之處是緩存所需的其他邏輯成本(就硅片面積和功耗而言)。
圖4:緩存的能量?jī)?yōu)勢(shì)
圖4摘自普林斯頓(Brooks,2000)一份論文,顯示了針對(duì)某簡(jiǎn)單應(yīng)用基準(zhǔn)的三套數(shù)據(jù)。針對(duì)不同的緩存大小,這些條塊分別代表性能IP C(單位周期指令數(shù))、功耗和功耗時(shí)間積(ED P)??偟膩?lái)說(shuō),性能會(huì)隨著緩存大小的增加而提升。但是,系統(tǒng)的功耗也會(huì)增加,因?yàn)樵龃缶彺鎲卧獣?huì)相應(yīng)增加功耗。功耗時(shí)間積允許我們?cè)谛阅芎途彺娲笮≈g取得平衡。在這個(gè)例子里,存在一個(gè)最佳點(diǎn),即緩存大小為64k時(shí),此時(shí)的功耗時(shí)間積最小。
最大限度減少數(shù)據(jù)內(nèi)存存取
A RM架構(gòu)的一個(gè)特性是其常量是不確定的,特別是,不可能用單條指令把一個(gè)任意32位常量放到一個(gè)寄存器中。實(shí)際上,所有內(nèi)存存取必須按寄存器中的地址操作,這就意味著程序需要把這些地址和其他常量頻繁地放到寄存器中,而這一點(diǎn)很難做到。解決此問(wèn)題的標(biāo)準(zhǔn)方法是把常量作為文字?jǐn)?shù)據(jù)嵌入到代碼段中,在運(yùn)行時(shí)使用PC相關(guān)的加載進(jìn)行加載。
因此,這種最大限度減少常量影響的方法很實(shí)用。確保在編譯時(shí)這些常量是已知的,如果可能,最好能把這些常量嵌入到單條ARM指令中。為了存取全局變量,盡可能減少加載基址指針的需求。這就需要確保全局變量在運(yùn)行時(shí)都在內(nèi)存中,這樣才能使用單個(gè)指針存取多個(gè)變量。實(shí)現(xiàn)這個(gè)目標(biāo)最簡(jiǎn)單的方式是將全局變量放到一個(gè)結(jié)構(gòu)中。
盡管A R M的堆棧訪問(wèn)相對(duì)高效(堆棧訪問(wèn)可較好地加載和存儲(chǔ)多條指令),但是程序員還可以通過(guò)很多方式來(lái)減少堆棧訪問(wèn):減少活動(dòng)變量、避免占用本地變量地址、可能時(shí)充分利用尾部調(diào)用優(yōu)化、將傳遞到函數(shù)的參數(shù)數(shù)量減少到四個(gè)以下、允許編譯器主動(dòng)內(nèi)聯(lián)函數(shù)等。
遞歸情形和避免遞歸情形的做法更加復(fù)雜。通常編譯器可以對(duì)歸函數(shù)很好地進(jìn)行尾部?jī)?yōu)化。實(shí)際上將所有數(shù)據(jù)存儲(chǔ)到堆棧中可以比其他做法獲得更好的局部性?;蛟S建議可能最好表達(dá)為“除非其他做法讓數(shù)據(jù)局部性更糟或您確信編譯器可以對(duì)遞歸調(diào)用進(jìn)行尾部?jī)?yōu)化,否則不要使用遞歸算法”。應(yīng)編寫(xiě)異常處理程序,增加尾部連鎖的機(jī)會(huì),進(jìn)而避免堆棧環(huán)境內(nèi)不必要的保存和恢復(fù)。
現(xiàn)在我們把注意力轉(zhuǎn)到這個(gè)問(wèn)題的第二頭大象,即指令執(zhí)行。
最大限度減少指令數(shù)目
事實(shí)上,減少指令執(zhí)行次數(shù)本質(zhì)上與性能優(yōu)化是相同的,執(zhí)行的指令數(shù)越少,能耗就越低。另外,還要增加一些明顯的指針。
首先,正確地配置工具。在編譯器和鏈接器完全了解目標(biāo)平臺(tái),甚至無(wú)法實(shí)施一些基本的優(yōu)化。
編寫(xiě)代碼時(shí)要保持敏銳,才能避免不必要的操作。對(duì)于A R M架構(gòu),32位數(shù)據(jù)類(lèi)型是高效的:一般8位和16位數(shù)據(jù)類(lèi)型,盡管占用的存儲(chǔ)空間