RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

不同思路的寄存器分配算法

openEuler ? 來源:畢昇編譯 ? 作者:王博洋 ? 2022-08-24 10:17 ? 次閱讀

概念介紹

在介紹算法之前,我們回顧下基本概念:

  • |X|:X的度數(shù),(無向圖中)節(jié)點(diǎn)的鄰居個(gè)數(shù)。

  • CFG:控制流圖。

  • successor:本文指CFG中基本塊的后繼。

  • 四元式:(op,result,arg1,arg2),比如常見的a=b+c就可以看作四元式(+,a,b,c)。

  • SSA(Static Single Assignment):靜態(tài)單賦值。

  • use/def:舉個(gè)例子,對于指令n: c <- c+b來說 use[n]={c,b},def[n]={c}。

  • live-in:當(dāng)以下任一條件滿足時(shí),則稱變量a在節(jié)點(diǎn)n中是live-in的,寫作a∈in[n]。節(jié)點(diǎn)n本文中代表指令。

  1. a∈use[n];
  2. 存在從節(jié)點(diǎn)n到其他節(jié)點(diǎn)的路徑使用了a且不包括a的def。
  • live-out: 變量a在節(jié)點(diǎn)n的任一后繼的live-in集合中。寫作a∈out[n]

  • 干涉:在某一時(shí)刻,兩個(gè)變量在同一live-in集合中。

  • RIG(Register Interfere Graph): 無向圖,其點(diǎn)集和邊集構(gòu)成如下:

  • 節(jié)點(diǎn):變量
  • 邊:如果兩節(jié)點(diǎn)存在干涉,那么這兩節(jié)點(diǎn)之間就有一條干涉邊
  • k-著色:給定無向圖G=(V,E),其中V為頂點(diǎn)集合,E為邊集合。將V分為k個(gè)組,每組中沒有相鄰頂點(diǎn),可稱該圖G是k著色的。當(dāng)然可著色前提下,k越小越好。

需要注意的是,我們后續(xù)的算法會(huì)作用在最普通的四元式上,而不是SSA。在介紹寄存器分配算法之前,我們需要活躍變量分析來構(gòu)建干涉圖。

活躍變量分析與圖著色算法

活躍變量分析

簡單來說,就是計(jì)算每個(gè)點(diǎn)上有哪些變量被使用。

算法描述如下[1]

input: CFG = (N, E, Entry, Exit)
begin
// init
for each basic block B in CFG
in[B] = ?
// iterate
do{
for each basic block B other than Exit{
out[B] = ∪(in[s]),for all successors s of B
in[B] = use[B]∪(out[B]-def[B])
}
}until all in[] do't change

活躍變量分析還有孿生兄弟叫Reaching Definitions,不過實(shí)現(xiàn)功能類似,不再贅述。

舉個(gè)例子:對圖1的代碼進(jìn)行活躍變量分析

4c584276-22c3-11ed-ba43-dac502259ad0.png

圖1[2]

可以得到每個(gè)點(diǎn)的活躍變量如圖2所示:

4c7c6d68-22c3-11ed-ba43-dac502259ad0.png

圖2

過程呢?限于篇幅,僅僅計(jì)算第一輪指令1的結(jié)果,剩余部分讀者可自行計(jì)算。

步驟 下標(biāo) out in
第一次迭代 1 {} {b,c}
... ... ... ...

可畫出RIG如圖3:

4c8d3bde-22c3-11ed-ba43-dac502259ad0.png

圖3

圖著色

經(jīng)過上文的活躍變量分析,我們得到了干涉圖,下一步對其進(jìn)行上色。

但是圖著色是一個(gè)NP問題,我們會(huì)采用啟發(fā)式算法對干涉圖進(jìn)行著色?;舅悸肥牵?/p>

  1. 找到度小于k的節(jié)點(diǎn);
  2. 從圖中刪除;
  3. 判斷是否為可著色的圖;
  4. 迭代運(yùn)行前3步直到著色完成。

算法描述[3]

input: RIG, k
// init
stack = {}
// iterate
while RIG != {} {
t := pick a node with fewer than k neighbors from RIG // 這里RIG可以先按度數(shù)排序節(jié)點(diǎn)再返回
stack.push(t)
RIG.remove(t)
}
// coloring
while stack != {} {
t := stack.pop()
t.color = a color different from t's assigned colored neighbors
}

對于例子1,假設(shè)有4個(gè)寄存器r1、r2、r3、r4可供分配。

步驟 stack RIG
0 {} 4c8d3bde-22c3-11ed-ba43-dac502259ad0.png
1 {a}

4cb49e2c-22c3-11ed-ba43-dac502259ad0.png

2 {d,a}

4ccf088e-22c3-11ed-ba43-dac502259ad0.png

3 {c,d,a}

4cd9b4fa-22c3-11ed-ba43-dac502259ad0.png

4 {b,c,d,a}

4ced3430-22c3-11ed-ba43-dac502259ad0.png

5 {e,b,c,d,a}

4cfbeae8-22c3-11ed-ba43-dac502259ad0.png

6 {f,e,b,c,d,a}
寄存器分配 stack

4d0e7dfc-22c3-11ed-ba43-dac502259ad0.png

{e,b,c,d,a}

4d243d22-22c3-11ed-ba43-dac502259ad0.png

{b,c,d,a}

4d38f3d4-22c3-11ed-ba43-dac502259ad0.png

{c,d,a}

4d437052-22c3-11ed-ba43-dac502259ad0.png

{d,a}

4d52c2b4-22c3-11ed-ba43-dac502259ad0.png

{a}

4d747c88-22c3-11ed-ba43-dac502259ad0.png

{}

所以圖3中的RIG是4-著色的。但如果只有三種顏色可用,怎么辦呢?

沒關(guān)系,我們還有大容量的內(nèi)存,雖然速度慢了那么一點(diǎn)點(diǎn)。著色失敗就把變量放在內(nèi)存里,用的時(shí)候再取出來。

依然是上例,但是k=3,只有三個(gè)顏色。

步驟 stack RIG
0 {}

4c8d3bde-22c3-11ed-ba43-dac502259ad0.png

1 {a}

4cb49e2c-22c3-11ed-ba43-dac502259ad0.png

2 沒有度數(shù)小于3的節(jié)點(diǎn)了,需要溢出變量了 /

如果f的鄰居是2-著色的就好了,但不是。那就只能選一個(gè)變量存入內(nèi)存了。這里我們選擇將變量f溢出至內(nèi)存。溢出后的IR和RIG如圖:

4da4d202-22c3-11ed-ba43-dac502259ad0.png

圖4 溢出后的IR

4dbbbd8c-22c3-11ed-ba43-dac502259ad0.png

圖5 溢出后的RIG

所以,溢出其實(shí)是分割了變量的生命周期以降低被溢出節(jié)點(diǎn)的鄰居數(shù)量。溢出后的著色圖如圖6:

4dd239a4-22c3-11ed-ba43-dac502259ad0.png

圖6著色后的圖5

這里溢出變量f并不是明智的選擇,關(guān)于如何優(yōu)化溢出變量讀者可自行查閱資料。

至此,圖著色算法基本介紹完畢。不過,如果代碼中的復(fù)制指令,應(yīng)該怎么處理呢?

寄存器分配之前會(huì)有Copy Propagation和Dead Code Elimination優(yōu)化掉部分復(fù)制指令,但是兩者并不是全能的。

比如:代碼段1中,我們可以合并Y和X。但是代碼段2中Copy Propagation就無能為力了,因?yàn)榉种?huì)導(dǎo)致不同的Y值。

// 代碼段1
X = ...
A = 10
Y = X
Z = Y + A
return Z

// 代碼段2
X= A + B
Y = C
if (...) {Y = X}
Z = Y + 4

所以,寄存器分配算法也需要對復(fù)制指令進(jìn)行處理。如何處理?給復(fù)制指令的源和目標(biāo)分配同一寄存器。

那么如何在RIG中表示呢?如果把復(fù)制指令的源和目標(biāo)看作RIG中相同的節(jié)點(diǎn),自然會(huì)分配同一寄存器。

  • 相同節(jié)點(diǎn)?可以擴(kuò)展RIG:新增虛線邊,代表合并候選人。

  • 成為合并候選人的條件是:如果X和Y的生命周期不重合,那么對于Y=X指令中的X和Y是可合并的。

  • 為了保證合并合法且不造成溢出:合并后局部的度數(shù)

那么如何計(jì)算局部的度數(shù)?介紹三種算法:

  • 簡單算法
  • Brigg's 算法
  • George's 算法
  1. 簡單算法:(|X|+|Y|),很保守的算法但是可能會(huì)錯(cuò)過一些場景

    比如k=2時(shí),圖7應(yīng)用簡單算法是沒辦法合并的

    4de756a4-22c3-11ed-ba43-dac502259ad0.png

    圖7[3]

    但明顯圖7可以合并成圖8:

    4dff6e2e-22c3-11ed-ba43-dac502259ad0.png

    圖8[3]

  2. Brigg's 算法:X和Y可合并,如果X和Y中度數(shù)≥k的鄰居個(gè)數(shù)<k。但是如果X的度數(shù)很大,算法效率就不高

  3. George's算法:X和Y可合并,如果對Y的每個(gè)鄰居T,|T|

    ?比如k=2時(shí),圖9就可以合并X和Y。

    4e16fcce-22c3-11ed-ba43-dac502259ad0.png

    圖9[3]

    相對于Brigg算法、George算法不用遍歷節(jié)點(diǎn)的鄰居。注意,圖著色時(shí)可以按節(jié)點(diǎn)度數(shù)從小到大依次訪問。

到此,圖著色算法介紹完畢。

線性掃描

接下來介紹一種不同思路的算法:線性掃描。算法描述如下[4]

LinearScanRegisterAllocation:
active := {}
for i in live interval in order of increasing start point
ExpireOldIntervals(i)
if length(avtive) == R
SpillAtInterval(i)
else
register[i] := a regsiter removed from pool of free registers
add i to active, sorted by increasing end point
ExpireOldInterval(i)
for interval j in active, in order of increaing end point
if endpoint[j] >= startpoint[i]
return
remove j from active
add register[j] to pool of free registers
SpillAtInterval(i)
spill := last interval in active
if endpoint[spill] > endpoint[i]
register[i] := register[spill]
location[spill] := new stack location
remove spill from active
add i to active, sorted by increasing end point
else
location[i] := new stack location

live interval其實(shí)就是變量的生命期,用活躍變量分析可以算出來。不過需要標(biāo)識(shí)第一次出現(xiàn)和最后一次出現(xiàn)的時(shí)間點(diǎn)。

舉個(gè)例子:

4e2cd620-22c3-11ed-ba43-dac502259ad0.png

圖10

變量名 live interval
a 1,2
d 2,3,4,5
e 3,4,5,6

llvm中實(shí)現(xiàn)

在上文中介紹的算法都是作用在最普通的四元式上,但LLVM-IR是SSA形式,有PHI節(jié)點(diǎn),但PHI節(jié)點(diǎn)沒有機(jī)器指令表示,所以在寄存器分配前需要把PHI節(jié)點(diǎn)干掉,消除PHI節(jié)點(diǎn)的算法限于篇幅不展開,如感興趣的話請后臺(tái)留言。

llvm作為工業(yè)級編譯器,有多種分配算法,可以通過llc的命令行選項(xiàng)-regalloc=pbqp|greedy|basic|fast來手動(dòng)控制分配算法。

不同優(yōu)化等級默認(rèn)使用算法也不同:O2和O3默認(rèn)使用greedy,其他默認(rèn)使用fast。

fast算法的策略很簡單,掃描代碼并為出現(xiàn)的變量分配寄存器,寄存器不夠用就溢出到內(nèi)存。用途主要是調(diào)試。

basic算法以linearscan為基礎(chǔ)并對life interval設(shè)置了溢出權(quán)重而且用優(yōu)先隊(duì)列來存儲(chǔ)life interval。

greedy算法也使用優(yōu)先隊(duì)列,但特點(diǎn)是先為生命期長的變量分配寄存器,而短生命期的變量可以放在間隙中,詳情可以參考[5]。

pbqp算法全稱是Partitioned Boolean Quadratic Programming,限于篇幅,感興趣的讀者請查閱[6]

至于具體實(shí)現(xiàn),自頂向下依次是:

  • TargetPassConfig::addMachinePasses含有寄存器分配和其他優(yōu)化
  • addOptimizedRegAlloc中是與寄存器分配密切相關(guān)的pass,比如上文提到的消除PHI節(jié)點(diǎn)
  • addRegAssignAndRewriteOptimized是實(shí)際的寄存器分配算法
  • 寄存器分配相關(guān)文件在lib/CodeGen下的RegAllocBase.cpp、RegAllocGreedy.cpp、RegAllocFast.cpp、RegAllocBasic.cpp和RegAllocPBQP.cpp等。
  • RegAllocBase類定義了一系列接口,重點(diǎn)是selectOrSplit和enqueue/dequeue方法,數(shù)據(jù)結(jié)構(gòu)的重點(diǎn)是priority queue。selectOrSplit方法可以類比上文中提到的SpillAtInterval。priority queue類比active list。簡要代碼如下:
voidRegAllocBase::allocatePhysRegs(){
//1.virtualreg其實(shí)就是變量
while(LiveInterval*VirtReg=dequeue()){

//2.selectOrSplit會(huì)返回一個(gè)可用的物理寄存器然后返回新的liveintervals列表
usingVirtRegVec=SmallVector4>;
VirtRegVecSplitVRegs;
MCRegisterAvailablePhysReg=selectOrSplit(*VirtReg,SplitVRegs);
//3.分配失敗檢查
if(AvailablePhysReg==~0u){
...
}
//4.正式分配
if(AvailablePhysReg)
Matrix->assign(*VirtReg,AvailablePhysReg);

for(RegisterReg:SplitVRegs){
//5.入隊(duì)分割后的liverinterval
LiveInterval*SplitVirtReg=&LIS->getInterval(Reg);
enqueue(SplitVirtReg);
}
}
}

至于這四種算法的性能對比,我們主要考慮三個(gè)指標(biāo):運(yùn)行時(shí)間、編譯時(shí)間和溢出次數(shù)。

4e3c515e-22c3-11ed-ba43-dac502259ad0.png

圖11 各算法的運(yùn)行時(shí)間,圖源[6]

橫坐標(biāo)是測試集,縱坐標(biāo)是以秒為單位的運(yùn)行時(shí)間

4e73d2d2-22c3-11ed-ba43-dac502259ad0.png

12各算法的編譯時(shí)間,圖源[6]

橫坐標(biāo)是測試集,縱坐標(biāo)是編譯時(shí)間

4e857aaa-22c3-11ed-ba43-dac502259ad0.png

13 各算法的溢出次數(shù),圖源[6]

從這三幅圖可以看出greedy算法在大多數(shù)測試集上都優(yōu)于其他算法,因此greedy作為默認(rèn)分配器是可行的。

小結(jié)

我們通過一個(gè)例子介紹了活躍變量分析和圖著色算法。借助活躍變量分析,我們知道了變量的生命期,有了變量生命期建立干涉圖,對干涉圖進(jìn)行著色。如果著色失敗,可以選擇某個(gè)變量溢出到內(nèi)存中。之后在RIG的基礎(chǔ)上介紹了寄存器合并這一變換。

然后我們簡單介紹了不同思路的寄存器分配算法:linearscan。最后介紹了llvm12中算法的實(shí)現(xiàn)并對比了llvm中四種算法的性能差異。

審核編輯:湯梓紅


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報(bào)投訴
  • 寄存器
    +關(guān)注

    關(guān)注

    31

    文章

    5336

    瀏覽量

    120230
  • 算法
    +關(guān)注

    關(guān)注

    23

    文章

    4607

    瀏覽量

    92828
  • 編譯器
    +關(guān)注

    關(guān)注

    1

    文章

    1623

    瀏覽量

    49108

原文標(biāo)題:編譯器優(yōu)化那些事兒(5):寄存器分配

文章出處:【微信號:openEulercommunity,微信公眾號:openEuler】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    線性匯編-寄存器分配疑問 請問為什么不同的變量分配了相同的寄存器?

    上面是線性匯編函數(shù),下圖為寄存器分配,為什么不同的變量分配了相同的寄存器???如何使一個(gè)變量分配一個(gè)寄存
    發(fā)表于 08-07 09:06

    編譯優(yōu)化那些事兒(5):寄存器分配

    色。如果著色失敗,可以選擇某個(gè)變量溢出到內(nèi)存中。之后在RIG的基礎(chǔ)上介紹了寄存器合并這一變換。然后我們簡單介紹了不同思路寄存器分配算法:l
    發(fā)表于 08-24 14:41

    寄存器與移位寄存器

    寄存器與移位寄存器 寄存器是用來寄存數(shù)碼的邏輯部件,所以必須具備接收和寄存數(shù)碼的功能。任何一種觸發(fā)
    發(fā)表于 03-12 15:19 ?59次下載

    寄存器應(yīng)用舉例

    寄存器應(yīng)用舉例   在9.2.3寄存器的應(yīng)用一節(jié)中,曾介紹利用寄存器集成芯片74LS194構(gòu)造的兩種脈沖分配器:環(huán)形計(jì)數(shù)和扭環(huán)形計(jì)數(shù)
    發(fā)表于 05-17 00:02 ?1647次閱讀
    <b class='flag-5'>寄存器</b>應(yīng)用舉例

    什么是Register Pressure(寄存器不足) /

    什么是Register Pressure(寄存器不足) / Register Renaming(寄存器重命名)?   Register Pressure(寄存器不足) 軟件算法執(zhí)行時(shí)
    發(fā)表于 02-04 11:02 ?1343次閱讀

    寄存器,寄存器是什么意思

    寄存器,寄存器是什么意思 寄存器定義  寄存器是中央處理內(nèi)的組成部分。寄存器是有限存貯容量
    發(fā)表于 03-08 14:26 ?2.2w次閱讀

    數(shù)據(jù)寄存器,數(shù)據(jù)寄存器是什么意思

    數(shù)據(jù)寄存器,數(shù)據(jù)寄存器是什么意思 數(shù)據(jù)寄存器數(shù)據(jù)寄存器包括累加AX、基址寄存器BX、計(jì)數(shù)
    發(fā)表于 03-08 14:38 ?1.3w次閱讀

    移位寄存器,移位寄存器是什么意思

    移位寄存器,移位寄存器是什么意思 移位寄存器_
    發(fā)表于 03-08 14:50 ?1.8w次閱讀

    寄存器組網(wǎng)絡(luò)處理上的寄存器分配技術(shù)

    本內(nèi)容提供了多寄存器組網(wǎng)絡(luò)處理上的寄存器分配技術(shù)
    發(fā)表于 06-28 15:26 ?28次下載
    多<b class='flag-5'>寄存器</b>組網(wǎng)絡(luò)處理<b class='flag-5'>器</b>上的<b class='flag-5'>寄存器</b><b class='flag-5'>分配</b>技術(shù)

    寄存器與移位寄存器

    寄存器與移位寄存器:介紹寄存器原理和移位寄存器的原理及實(shí)現(xiàn)。
    發(fā)表于 05-20 11:47 ?0次下載

    高效的C編程之寄存器分配

    14.7 寄存器分配 編譯一項(xiàng)很重要的優(yōu)化功能就是對寄存器分配。與分配
    發(fā)表于 10-17 17:17 ?4次下載

    AD轉(zhuǎn)換寄存器設(shè)置

    AD轉(zhuǎn)換寄存器設(shè)置AD轉(zhuǎn)換寄存器設(shè)置AD轉(zhuǎn)換寄存器設(shè)置
    發(fā)表于 11-10 17:36 ?16次下載
    AD轉(zhuǎn)換<b class='flag-5'>寄存器</b>設(shè)置

    ARM通用寄存器及狀態(tài)寄存器詳解

    筆者來聊聊ARM通用寄存器以及狀態(tài)寄存器的認(rèn)識(shí)與理解。
    的頭像 發(fā)表于 01-06 14:58 ?7142次閱讀

    什么是編譯算法寄存器分配

    寄存器是CPU中的稀有資源,如何高效的分配這一資源是一個(gè)至關(guān)重要的問題。本文介紹了基于圖著色的寄存器分配算法。
    的頭像 發(fā)表于 03-02 16:11 ?1118次閱讀
    什么是編譯<b class='flag-5'>器</b><b class='flag-5'>算法</b>之<b class='flag-5'>寄存器</b><b class='flag-5'>分配</b>

    寄存器分為基本寄存器和什么兩種

    寄存器是計(jì)算機(jī)中用于存儲(chǔ)數(shù)據(jù)的高速存儲(chǔ)單元,它們是CPU內(nèi)部的重要組成部分。寄存器可以分為基本寄存器和擴(kuò)展寄存器兩種類型。 一、基本寄存器
    的頭像 發(fā)表于 07-12 10:31 ?1312次閱讀
    RM新时代网站-首页