OpenEM 簡(jiǎn)介和基于 OpenEM 的大矩陣乘實(shí)現(xiàn)
7. 配置 preload 門限,參見本文 1.2 節(jié)的敘述。
2.2.2 創(chuàng)建生產(chǎn)者/消費(fèi)者場(chǎng)景
前面介紹過,在 OpenEM 中,消費(fèi)者就是 execution object,溝通生產(chǎn)者和消費(fèi)者的管道就是queue。本小節(jié)介紹怎樣創(chuàng)建 execution object 和 queue 以及怎樣把它們關(guān)聯(lián)起來。 關(guān)于怎樣產(chǎn)生 event,本文在下一小節(jié)描述。OpenEM 有下列 API 供應(yīng)用調(diào)用:
• 調(diào)用 em_eo_create()可以創(chuàng)建 execution object
• 調(diào)用 em_queue_create()可以創(chuàng)建 queue
• 調(diào)用 em_eo_add_queue()可以把 queue 和 execution object 映射起來
本演示用例通過參數(shù)配置表列出 execution object, queue group object 和 queue object 的參數(shù),然后通過解析函數(shù)解析配置表再調(diào)用 OpenEM的 API,這樣各個(gè)軟件對(duì)象的參數(shù)在配置表中一目了然,代碼的可讀性較好。圖 5 是本演示用例的映射關(guān)系。
需要注意的是 coremask 總共有 64 個(gè)比特,但是目前 6678 最多也只有 8 個(gè) DSP 核。所以大量 mask 比特是用不到的,目前。核 0~7 對(duì)應(yīng)的 mask 比特是位于 byte[4]的 bit0:7
需要注意的是 queue 到 execution object 的映射是通過 receiver 函數(shù)關(guān)聯(lián)起來,如紅色高亮顯示部分。
初始化job的偽代碼如下:
2.2.3 產(chǎn)生 event
本文的演示用例把 matrix Y 切分成了 128 個(gè) 2048*16 的子塊,每個(gè) event 對(duì)應(yīng)一個(gè)子塊。Event被發(fā)送給 execution object 以后,receive 函數(shù)計(jì)算 Matrix X 乘與 matrix Y block,即 100*2048 ×2048*16 的矩陣乘,產(chǎn)生 100*16 個(gè)輸出。event 的產(chǎn)生包括下面幾個(gè)簡(jiǎn)單步驟:
• 調(diào)用 em_alloc 函數(shù),從 public pool 獲取 free 的 event 描述符并且 enable preloading。
• 把待處理的數(shù)據(jù)緩沖區(qū)掛到描述符上,也就是把描述符的 buffer 指針指向這個(gè)數(shù)據(jù)緩沖區(qū)。
• 在描述符的 software info 域填上 job index。
• 調(diào)用 em_send,把 event 發(fā)送到對(duì)應(yīng)的 queue,也就是 proc queue。
下面是產(chǎn)生 event 的代碼:
需要注意的是 Event 產(chǎn)生的時(shí)候,它被哪一個(gè) execution object 處理還沒有確定。因?yàn)?execution object 只是和 queue 關(guān)聯(lián)的。當(dāng)把 event 發(fā)送到一個(gè) queue 的時(shí)候,負(fù)責(zé)處理 event 的 execution object 就確定了。所以在調(diào)用 em_send()發(fā)送 event 到 queue 的時(shí)候參數(shù)之一就是要發(fā)送到的queue 的 handler。
2.2.4 運(yùn)行和 exit
如前所述,“矩陣乘 event”是通過 proc queue 發(fā)給 scheduler 的,所以它被 proc queue 映射到mat_mpy calc 這個(gè) execution object 上。Dispatcher 收到這個(gè) event 后就調(diào)用“mat_mpy calc”對(duì)應(yīng)的 receiver 函數(shù)計(jì)算矩陣相乘。因?yàn)?proc queue 所屬的 queue group 是映射到所有 DSP 核的,所以 128 個(gè)“矩陣乘 event”是在所有核上并行處理的。每個(gè)核處理完 event 后就把它釋放回global free pool。這樣這個(gè) event 又成為一個(gè) free 的 event。
如 2.2.3 節(jié)所述,主核可以通過查詢 global free pool 的描述符個(gè)數(shù)是否恢復(fù)來判斷是否所有“矩陣乘 event”已經(jīng)處理完。
當(dāng)所有“矩陣乘 event”處理完后,主核再產(chǎn)生 8 個(gè)“exit event”發(fā)送到 exit queue。理論上scheduler 可以把 exit job 調(diào)度給任意一個(gè)核,而不會(huì)保證每個(gè)核一個(gè) exit job。所以 exit job 中的處理比較特殊。exit job 的 receiver 函數(shù)直接執(zhí)行系統(tǒng)調(diào)用 exit(0)。這樣就不會(huì)返回到 Dispatcher,也不會(huì)再發(fā)出 prefetch command。而另一方面,scheduler 是在收到 DSP 核的 prefetch command 以后才把 event 調(diào)度給這個(gè)核的。這個(gè)機(jī)制保證了每個(gè)核收到且僅收到一個(gè)“exit event”。
在 exit job 的 receiver 函數(shù)中,主核執(zhí)行的分支稍有差異。主核需要先做完結(jié)果的校驗(yàn)再執(zhí)行系統(tǒng)調(diào)用 exit(0)。所以在板上運(yùn)行是會(huì)觀察到其他核很快(小于 1s)就從 run 狀態(tài)轉(zhuǎn)換到 abort 狀態(tài),而主核保持 run 了很長(zhǎng)時(shí)間(大約 50s)才進(jìn)入 abort 狀態(tài)。原因是:在主核上執(zhí)行結(jié)果驗(yàn)證工作時(shí)產(chǎn)生校驗(yàn)結(jié)果的函數(shù)計(jì)算耗時(shí)比較長(zhǎng)。
下面是 exit job 的 receiver 函數(shù)的代碼主干:
2.3 基于 OpenEM 的大矩陣乘性能測(cè)試結(jié)果
2.3.1 算法代碼和 cycle 數(shù)的理論極限
設(shè) r1 是 X 矩陣的行數(shù),c1 是 X 矩陣的列數(shù),c2 是 Y 矩陣的列數(shù)。在我們的演示用例中 r1 =100, c1 = 2048, c2 = 2048。如前所述,Receiver 函數(shù)要計(jì)算 100*2048 × 2048*16 的矩陣乘,對(duì)應(yīng)下面的偽代碼:
循環(huán)內(nèi)核是 4 個(gè) cycle。 如果只考慮循環(huán)內(nèi)核消耗的 cycle 數(shù),計(jì)算 100*2048 × 2048*16 的矩陣乘需要的 cycle 數(shù)是 100/2*16/2*2048/4*4 = 819,200 cycle。整個(gè) X*Y=Z 包括計(jì)算 128 個(gè)這樣的矩陣乘。所以總的 cycle 數(shù)是 819,200*128 = 104,857,600 cycles。在 1Ghz 的 C66 核上這相當(dāng)于104.8ms。但是我們的上述理論計(jì)算沒有考慮循環(huán)的前后綴消耗的 cycle 數(shù),也沒有考慮 cache miss stall 的等待時(shí)間。在 6678EVM 板的單個(gè) DSP 核上實(shí)測(cè),計(jì)算 X*Y=Z 消耗的實(shí)際時(shí)間是190,574,214 cycles。相當(dāng)于 190ms。
評(píng)論