OpenEM 簡(jiǎn)介和基于 OpenEM 的大矩陣乘實(shí)現(xiàn)
摘要
OpenEM 的全稱是 Open Event Machine。是 TI 針對(duì)嵌入式應(yīng)用開(kāi)發(fā)的 multicore runtime system library。OpenEM 可以在多核上有效的調(diào)度,分發(fā)任務(wù)。它把任務(wù)調(diào)度給負(fù)載輕的核,進(jìn)而實(shí)現(xiàn)動(dòng)態(tài)的負(fù)載平衡。OpenEM 是基于 TI Keystone 系列芯片的 multicore Navigator 構(gòu)建的,具有開(kāi)銷小,效率高的特點(diǎn)。本文首先對(duì) OpenEM 的原理做了簡(jiǎn)單的介紹。然后結(jié)合一個(gè)大矩陣乘的演示用例詳細(xì)介紹了 OpenEM 的使用。最后通過(guò)量化分析這個(gè)演示用例的執(zhí)行cycle 數(shù),總結(jié)了 OpenEM 的效率和局限。希望本文能成為學(xué)習(xí) OpenEM 的讀者的一個(gè)有用的參考。
1、OpenEM 簡(jiǎn)介
OpenEM 的全稱是 Open Event Machine。它是 TI 開(kāi)發(fā)的可應(yīng)用于 Keystone 多核 DSP 的multicore runtime system library。OpenEM 的目的是在多核上有效的調(diào)度,分發(fā)任務(wù),實(shí)現(xiàn)動(dòng)態(tài)的負(fù)載平衡?;?OpenEM,用戶可以很容易的把原來(lái)的單核應(yīng)用移植到 Keystone 多核芯片。需要注意的是 OpenEM 目前只能把任務(wù)調(diào)度分發(fā)到同一個(gè) DSP 的多個(gè)核上,不能跨 DSP 調(diào)度分發(fā)。 OpenEM不依賴于 BIOS。它可以在芯片上裸跑,代碼精簡(jiǎn),效率高。而且,OpenEM不同于業(yè)界已經(jīng)有 OpenMP 和 OpenCL 等開(kāi)放式的 multi-core runtime systems。它是針對(duì)嵌入式系統(tǒng)的設(shè)計(jì),更能滿足嵌入式設(shè)計(jì)的實(shí)時(shí)性要求。TI 的 keystone 架構(gòu)多核芯片中有 Multicore Navigator。它由 Queue Manager(簡(jiǎn)稱為 QMSS)和一系列 Packet DMA engine 構(gòu)成。OpenEM就是基于這套硬件系統(tǒng)構(gòu)建的。例如,OpenEM 的 scheduler 是運(yùn)行在 QMSS 的 PDSP(QMSS內(nèi)部的 RISC 處理器)上的。OpenEM的 preload 功能是通過(guò) QMSS 的 packet DMA 實(shí)現(xiàn)的。熟悉QMSS 的編程對(duì)學(xué)習(xí) OpenEM 很有幫助。OpenEM 是 MCSDK 的一個(gè)組件。它還在不斷的發(fā)展改進(jìn)中。本文對(duì) OpenEM 的介紹以及演示用例都是基于 BIOS MCSDK 2.01.02 的 OpenEM 1.0.0.2。
1.1 OpenEM 的軟件對(duì)象
下面通過(guò)列表和圖示介紹了 OpenEM的主要軟件對(duì)象。表 1 是 OpenEM 的主要軟件對(duì)象的列表。
需要注意的是,本文介紹的 OpenEM 的運(yùn)行模式是:Scheduler 運(yùn)行在 PDSP,Dispatcher 是“run to completion ”模式。
圖 1 是一個(gè)軟件對(duì)象關(guān)系圖,顯示出了表 1 中列舉的軟件對(duì)象。定義了 2 個(gè) queue group,5 個(gè)queue 和 3 個(gè) execution object。Queue group1 的 core mask 對(duì)應(yīng)核 0 和 1。所以來(lái)自 queue1,2,3,4 的 event 只能在核 0 和核 1 上執(zhí)行,因?yàn)檫@些 queue 屬于 queue group1。Queue group2 的 core mask 對(duì)應(yīng)核 2 和 3。所以來(lái)自 queue5 的 event 只能在核 2 和核 3 上執(zhí)行,因?yàn)閝ueue5 屬于 queue group2。execution object 1 和 queue 1,2,3 映射關(guān)聯(lián)。execution object 2 和queue 4 映射關(guān)聯(lián)。execution object 3 和 queue 5 映射關(guān)聯(lián)。圖中的藍(lán)線表示了 event 的行徑,紅線表示 command 的行徑。圖中的 SD queue 是 hardware queue,它不是一個(gè)軟件對(duì)象而是OpenEM內(nèi)部的組件。
1.2 OpenEM 的兩個(gè)重要概念
OpenEM中有兩個(gè)容易混淆的重要概念:prefetch 和 preload。
• Prefetch 是指每個(gè) DSP 核向 scheduler 發(fā)命令,告訴 scheduler“本核已經(jīng)空閑了,可以分配新的工作給本核了”。只有收到一個(gè)核的 prefetch 命令,scheduler 才會(huì)調(diào)度新的 event 給這個(gè)核。如果 DSP 核不發(fā)出 prefetch 命令,它就不會(huì)被分派任務(wù)。這是 OpenEM 的 scheduler的基本調(diào)度原則。
• Preload 和 event 的屬性有關(guān)。通常,event 的數(shù)據(jù)是位于 DDR 的。如果 DSP 核直接訪問(wèn)DDR 效率會(huì)比較低。所以,OpenEM 可以把 event 的數(shù)據(jù)通過(guò) QMSS 的 packet DMA 搬到DSP 核的 local L2。這個(gè)搬移的過(guò)程就是 preload。每個(gè) event 的數(shù)據(jù)是否做 preload 是可配的。每個(gè) event 在創(chuàng)建的時(shí)候都可以指定一個(gè) preload 屬性。Event 的 preload 屬性可以是:
– Preload disable, 即不做預(yù)搬移
– Preload up to sizeA,即做預(yù)搬移,但是最多只搬 sizeA bytes
– Preload up to sizeB,即做預(yù)搬移,但是最多只搬 sizeB bytes
– Preload up to sizeC,即做預(yù)搬移,但是最多只搬 sizeC bytes
– 其中 SizeA,SizeB 和 SizeC 是常數(shù),在 OpenEM 初始化的時(shí)候可以配置。
1. 3 OpenEM 的常用 API cycle 數(shù)
OpenEM的附帶開(kāi)銷是應(yīng)用最關(guān)注特性之一。所以我們實(shí)測(cè)了 OpenEM 常用 API 的 cycle 數(shù)如表2。需要注意的是:由于 OpenEM會(huì)負(fù)責(zé) cache 一致性的維護(hù),而有些 API 的處理過(guò)程中含有cache 一致性的維護(hù)操作。所以這些 API 的調(diào)用 cycle 數(shù)很大程度上取決于它對(duì)多大的數(shù)據(jù)緩沖區(qū)做了 cache 一致性的維護(hù)。本文測(cè)試這些 cycle 的場(chǎng)景使用的數(shù)據(jù)緩沖區(qū)的大小是是 4096 words(32bit)。
2、基于 OpenEM 的大矩陣乘實(shí)現(xiàn)
大矩陣相乘的目的是計(jì)算 X*Y = Z
矩陣 X 是(100 × 2048 )的浮點(diǎn)實(shí)數(shù)矩陣。
矩陣 Y 是(2048 × 2048 )的浮點(diǎn)實(shí)數(shù)矩陣。
矩陣 Z 是(100 × 2048 )的浮點(diǎn)實(shí)數(shù)矩陣。
由于矩陣 Y 的數(shù)據(jù)量很大,所以在多核 DSP 上可以把它拆分成多個(gè)子塊,交給多個(gè) DSP 核并行計(jì)算。如圖 2 所示。
2.1 基于 OpenEM 的大矩陣乘方案設(shè)計(jì)
2.1.1 Memory 使用
[!--empirenews.page--]Shannon DSP (6678)的內(nèi)存系統(tǒng)包括片內(nèi)的 LL2(local L2)和 SL2(shared L2)。加上片外的 DDR。LL2 的 size 是 512 Kbytes,每個(gè)核有一份 LL2。 SL2 的 size 是 4Mbytes,8 個(gè)核共享 SL2。DDR size 和硬件板卡設(shè)計(jì)有關(guān),一般在 1G bytes 以上。 C66x 核對(duì) LL2 的訪問(wèn)效率最高,對(duì) SL2 的訪問(wèn)效率稍差,對(duì) DDR 的訪問(wèn)效率最低。基于多種存儲(chǔ)區(qū)間的不同特性,我們對(duì)數(shù)據(jù)存儲(chǔ)位置按如下規(guī)劃(參見(jiàn)圖 3):
– 矩陣 X 的 size 是 800 Kbytes,存儲(chǔ)是 shared L2
– 矩陣 Y 的 size 是 16 Mbytes,存儲(chǔ)是 DDR
– 矩陣 Z 的 size 是 800 Kbytes,存儲(chǔ)是 shared L2
雖然矩陣 Y 存儲(chǔ)在 DDR,但是我們啟用了 OpenEM 的 preload 功能。Preload 就是通過(guò) QMSS 的 packet DMA 把待處理的 event 數(shù)據(jù)(通常位于 DDR)搬到被調(diào)度 core 的 LL2。所以 DSP 核運(yùn)行的時(shí)候不直接從 DDR 取數(shù)。這保證了 DSP 核的數(shù)據(jù)訪問(wèn)效率。
2.1.2 處理流程
OpenEM中要有一個(gè) DSP 核作為主核,其他核就是從核,主核要完成的工作較多。本文的演示用例中,核 0 是主核,核 1~7 是從核。主從核的分工差異如圖 4:
1. 初始化 QMSS 和 free pool。
2. OpenEM 的 global 初始化和 local 初始化。global 初始化是主核執(zhí)行。local 初始化是每個(gè)核各自執(zhí)行。Local 初始化要等 global 初始化完成才能開(kāi)始。所以,中間需要加一個(gè)barrier。Barrier 可以理解成一個(gè)同步點(diǎn),所有 DSP 核在這個(gè)點(diǎn)完成一次同步再繼續(xù)向下執(zhí)行。本演示用例的 Barrier 是通過(guò)共享內(nèi)存的軟件信號(hào)量實(shí)現(xiàn)的。
3. 主核構(gòu)造生產(chǎn)者/消費(fèi)者場(chǎng)景并產(chǎn)生待處理的 event。生產(chǎn)者在 OpenEM 中不是一個(gè)軟件對(duì)象。我們可以把產(chǎn)生 event 并發(fā)送到 queue 的函數(shù)認(rèn)為是生產(chǎn)者。消費(fèi)者就是 execution object,溝通生產(chǎn)者和消費(fèi)者的管道就是 queue。構(gòu)造生產(chǎn)者/消費(fèi)者場(chǎng)景就是創(chuàng)建execution object 和 queue 并且把它們關(guān)聯(lián)起來(lái)。
4. 主核和從核進(jìn)入 event 處理的過(guò)程。
5. 主核檢測(cè)到所有 event 都處理完成后為每個(gè) DSP 核(包括它自己)產(chǎn)一個(gè) exit job。
6. 主核和從核處理 exit job。從核直接調(diào)用 exit(0)退出。主核先做結(jié)果驗(yàn)證然后調(diào)用 exit(0)退出。
本文演示用例實(shí)現(xiàn)的幾個(gè)特點(diǎn)是:
• OpenEM 的 free pool 是由用戶初始化的。在初始化 free pool 的時(shí)候 event 描述符不指向數(shù)據(jù)緩沖區(qū)。等分配了一個(gè) event 的時(shí)候再在這個(gè) event 對(duì)應(yīng)的描述符上掛數(shù)據(jù)緩沖區(qū)。這樣可以避免不必要的數(shù)據(jù)拷貝(從 global buffer 拷貝到 event buffer)。
• 主核通過(guò)查詢 free pool 中的 event 個(gè)數(shù)是否恢復(fù)回初始值來(lái)判斷是否所有“矩陣乘 event”都處理。因?yàn)椋?/p>
– Free pool 在初始化以后有 N 個(gè) free event,
– 從中分配了若干個(gè) event 后,free event 就減少了相應(yīng)的個(gè)數(shù),
– 每個(gè) core 每處理完一個(gè) event 就把這個(gè) event 回收到 free pool,free pool 的 event 個(gè)數(shù)就加一。當(dāng) free pool 的 event 個(gè)數(shù)恢復(fù)回 N,就說(shuō)明所有 event 都處理完了。
2.2 基于 OpenEM 的大矩陣乘實(shí)現(xiàn)
在初始化 OpenEM之前首先要做 multicore Navigator 的初始化。包括:PDSP firmware 的download, Link RAM 的初始化, Memory region 的初始化還有 free pool (也就是 free descriptorqueue)的初始化。這不屬于本文介紹的范疇,本文直接介紹 OpenEM的初始化。
2.2.1 OpenEM 的 Global 初始化
OpenEM的 global 初始化通過(guò)調(diào)用 API 函數(shù) ti_em_init_global()完成的。這個(gè) API 的入?yún)⑹窍旅嫠镜慕Y(jié)構(gòu)體。其中所列的參數(shù)是本文的演示用例使用的配置參數(shù)。本文針對(duì)每個(gè)參數(shù)的作用做了注釋。了解了參數(shù)了含義,就能了解 OpenEM 的 global 初始化的大致做了些什么。
注釋:
1. OpenEM要使用 hardware queue 資源。hw_queue_base_idx 用來(lái)指定 OpenEM 從哪個(gè)hardware queue 開(kāi)始可用。
2. OpenEM 的少量操作需要多 DSP 核訪問(wèn)共享的數(shù)據(jù)結(jié)構(gòu)。是通過(guò) hardware semaphore 實(shí)現(xiàn)多核lock/unclock 的。所以通過(guò) hw_sem_idx 告訴 OpenEM該使用哪一個(gè) hardware semaphore。
3. 指定 preload 使用的 QMSS packet DMA 的通道的起始索引。QMSS packet DMA 有 32 個(gè) RX/TX channel。在 OpenEM 中,每個(gè) DSP core 要占用一個(gè) TX/RX channel。
4. 指定 preload 使用的 QMSS Tx queues 的起始索引。要和 dma_idx 對(duì)應(yīng)起來(lái)。QMSS 有 32 個(gè) TX queue,索引是 800~831。對(duì)應(yīng) QMSS packet DMA 的 TX channel 0~31。所以,如果前面配置的 dma_idx 是 0,那么這里配置的 dma_queue_base_idx 應(yīng)該是 800。
5. 指定 OpenEM local free pool 對(duì)應(yīng)的 free queue index。Local free pool 是和 preload 相關(guān)的。local free pool 在物理上是一個(gè) free descriptor queue。里面存儲(chǔ)著 2 個(gè) host 描述符。每個(gè)描述符對(duì)應(yīng)一個(gè) local L2 buffer。如果發(fā)生 preload,packet DMA 就從 free descriptor queue pop 描述符,然后把數(shù)據(jù)傳到描述符指向的 local L2 buffer。每個(gè) DSP 核有一個(gè) local free pool。例如,在我們的演示用例中 core0~7 對(duì)應(yīng)的 free descriptor queue 索引是 2050~2057。
6. 指定 OpenEM global free pool 的個(gè)數(shù)。每個(gè) global free pool 包括 4 個(gè)初始化參數(shù),例如{ globalFreePoolFdqIdx, TI_EM_COH_MODE_ON,TI_EM_BUF_MODE_GLOBAL_TIGHT,0}。參數(shù) 1是這個(gè) global free pool 對(duì)應(yīng)的 free queue index。接下來(lái)幾項(xiàng)是這個(gè) pool 中的 buffer 的屬性。Global free pool 是用來(lái)從中分配 free event 的。調(diào)用 em_alloc()的入?yún)⒅痪褪?free pool index。[!--empirenews.page--]
7. 配置 preload 門限,參見(jiàn)本文 1.2 節(jié)的敘述。
2.2.2 創(chuàng)建生產(chǎn)者/消費(fèi)者場(chǎng)景
前面介紹過(guò),在 OpenEM 中,消費(fèi)者就是 execution object,溝通生產(chǎn)者和消費(fèi)者的管道就是queue。本小節(jié)介紹怎樣創(chuàng)建 execution object 和 queue 以及怎樣把它們關(guān)聯(lián)起來(lái)。 關(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 映射起來(lái)
本演示用例通過(guò)參數(shù)配置表列出 execution object, queue group object 和 queue object 的參數(shù),然后通過(guò)解析函數(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 的映射是通過(guò) receiver 函數(shù)關(guān)聯(lián)起來(lái),如紅色高亮顯示部分。
初始化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 處理還沒(méi)有確定。因?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”是通過(guò) 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é)所述,主核可以通過(guò)查詢 global free pool 的描述符個(gè)數(shù)是否恢復(fù)來(lái)判斷是否所有“矩陣乘 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ì)算沒(méi)有考慮循環(huán)的前后綴消耗的 cycle 數(shù),也沒(méi)有考慮 cache miss stall 的等待時(shí)間。在 6678EVM 板的單個(gè) DSP 核上實(shí)測(cè),計(jì)算 X*Y=Z 消耗的實(shí)際時(shí)間是190,574,214 cycles。相當(dāng)于 190ms。[!--empirenews.page--]
2.3.2 基于 OpenEM 的性能測(cè)試結(jié)果
基于 OpenEM的演示用例實(shí)現(xiàn)過(guò)程中,DSP 代碼中嵌入了少量測(cè)試代碼收集運(yùn)行的 cycle 信息。每個(gè)核把自己處理每個(gè) event 的起始和結(jié)束時(shí)間記錄在內(nèi)存(我們通過(guò)一個(gè)全局 timer 來(lái)保證所有DSP 核記錄的時(shí)間戳在時(shí)間軸上是同步的)。這些時(shí)間戳用 CCS 存到主機(jī)做后處理分析。通過(guò)分析,我們可以得到 8 個(gè) DSP 核并行處理消耗的時(shí)間。還可以分析每個(gè) DSP 核的忙/閑區(qū)間。
測(cè)試結(jié)果是,從第一個(gè) event 開(kāi)始處理到最后一個(gè) event 處理完,總時(shí)間是 31,433,438 cycle,也就是 31.4ms。也就是說(shuō),通過(guò) OpenEM把單 DSP 核的工作負(fù)載平衡到 8 個(gè) DSP 核上能達(dá)到的DSP 核利用率是 190,574,214/(31,433,438*8)= 76%。
通過(guò)對(duì)時(shí)間戳的處理我們得到下面的運(yùn)行圖,“-”表示 receiver 函數(shù)處理 event 的區(qū)間,本文稱之為有效時(shí)間。“#”表示 receiver 之外的區(qū)間(也就是代碼在 dispatcher 中執(zhí)行的區(qū)間),本文稱之為調(diào)度開(kāi)銷。每個(gè)“-”和“#”刻度表示 100,000 CPU cycle。
從上面的執(zhí)行圖看,調(diào)度開(kāi)銷不小,占了大約 15~20%的時(shí)間。但是這只是表面的現(xiàn)象。實(shí)際上,調(diào)度開(kāi)銷的大部分時(shí)間里,Dispatcher 是在查詢 hardware queue,等待新的 event。這是因?yàn)閜reload 沒(méi)能及時(shí)完成導(dǎo)致的。因?yàn)橥瑫r(shí)給 8 個(gè)核做 preload 需要很大的數(shù)據(jù)搬移的流量。根據(jù)以往的測(cè)試結(jié)果。使用 QMSS 的 packet DMA 從 DDR3 輸入數(shù)據(jù)到 local L2 的流量大約是 4G bytes 每秒。那么 preload 8 個(gè) event 總的數(shù)據(jù)量是 4byte * 2048 rows * 16 columns * 8 core = 1M bytes,需要的時(shí)間是 1/4 ms。因?yàn)槊總€(gè)“-”和“#”刻度表示 100,000 CPU cycle,運(yùn)行圖中紅線長(zhǎng)度就代表 preload 8 個(gè) event 的時(shí)間,它非常接近 250,000 cycle。理論計(jì)算和實(shí)際值基本吻合,所以我們認(rèn)為調(diào)度延遲是 packet DMA 的傳輸流量不足導(dǎo)致的。
我們也測(cè)試了不使用 pre-load 的場(chǎng)景。觀測(cè)到 scheduler 調(diào)度一個(gè) event 的延遲大約是 1200 個(gè)C66 CPU cycle。但是 DSP 核處理一個(gè) event 的耗時(shí)增大到原來(lái)的 10 倍。所以,pre-load 雖然會(huì)導(dǎo)致 QMSS packet DMA 流量不足成為凸顯的瓶頸,但是從總體效率來(lái)看還是非常必要的。
細(xì)心的讀者可能會(huì)發(fā)現(xiàn) 76% + 20% = 96%,并不是 100%。我們分析時(shí)間戳發(fā)現(xiàn),8 個(gè) DSP 核同時(shí)運(yùn)行的場(chǎng)景下,每個(gè)核處理一個(gè) 100*2048 × 2048*16 的矩陣乘的時(shí)間比只有一個(gè) DSP 核運(yùn)行的場(chǎng)景下的時(shí)間稍長(zhǎng)。原因是: 我們的演示用例中 X 矩陣和 Z 矩陣是存儲(chǔ)在 shared L2 的, 8 個(gè)核同時(shí)運(yùn)行就會(huì)同時(shí)讀寫(xiě)這兩個(gè) buffer,導(dǎo)致產(chǎn)生 shared L2 的 bank 沖突。 所以性能下降了。
3、總結(jié)
OpenEM具有使用簡(jiǎn)單,功能實(shí)用,執(zhí)行高效的特點(diǎn)。能在 KeyStone 多核 DSP 上實(shí)現(xiàn)動(dòng)態(tài)的負(fù)載平衡。它一方面提供了強(qiáng)大的功能,另一方面也給應(yīng)用留出了很大的靈活性。例如,通過(guò)讓?xiě)?yīng)用初始化 free pool 方便了 buffer 的管理。OpenEM 的現(xiàn)有功能已經(jīng)能夠支持基本的應(yīng)用。隨著版本更新功能還將不斷完善。
Reference
Ref[1] ti.openem.white.paper.pdf 位于 OpenEM 安裝目錄