理解:TI C6000 数据存储处理与性能优化
? ? ?存儲(chǔ)器之于CPU好比倉(cāng)庫(kù)之于車間。車間加工過(guò)程中的原材料、半成品、成品等均需入出倉(cāng)庫(kù),生產(chǎn)效率再快,如果倉(cāng)庫(kù)周轉(zhuǎn)不善,也必然造成生產(chǎn)阻塞。如同倉(cāng)庫(kù)需要合理地規(guī)劃管理一般,數(shù)據(jù)存儲(chǔ)也需要恰當(dāng)?shù)奶幚砑记蓙?lái)提升CPU的運(yùn)算性能。
? ? ?本文基于TI C6000系列DSP,介紹了與運(yùn)算性能優(yōu)化有關(guān)的存儲(chǔ)器知識(shí)。針對(duì)具體的數(shù)據(jù)存儲(chǔ)問(wèn)題,給出相應(yīng)的代碼優(yōu)化策略,并將容易混淆的概念集中討論。?
?名詞說(shuō)明??
- EMIF: External Memory Interface
- PMC: Program Memory Controller
- DMC: Data Memory Controller
- SPC: Section Program Counter
?
存儲(chǔ)體沖突Vs存儲(chǔ)別名模糊[1]??
1. 存儲(chǔ)體(bank)沖突
C6000系列各DSP的片內(nèi)存儲(chǔ)器結(jié)構(gòu)有所不同,其中大多數(shù)采用交叉存取式存儲(chǔ)體結(jié)構(gòu),如圖1所示,方框中數(shù)字表示字節(jié)地址。因?yàn)槊總€(gè)bank都是一個(gè)單口存儲(chǔ)器,所以每個(gè)周期對(duì)每個(gè)bank只能有一次訪問(wèn)。例如兩個(gè)short型數(shù)據(jù)a和b,a存放在地址1-2中,b存放在地址8-9中,則程序中不能安排a和b并行存/取,否則會(huì)導(dǎo)致存儲(chǔ)器存取延遲,使流水線暫停一個(gè)周期,在暫停周期中進(jìn)行第二次讀/寫存儲(chǔ)器,這就是存儲(chǔ)體沖突現(xiàn)象。
?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖1 ?4bank交叉存取式存儲(chǔ)器
以16bit short型點(diǎn)積計(jì)算為例:
int dotp(short a[], short b[]);
一個(gè)高效的軟件流水核如下。在最后兩條指令中,用LDW“字加載”命令,一個(gè)周期中同時(shí)加載a[0]、a[1]、b[0]、b[1]。
要使軟件流水不被阻塞,則需保證數(shù)組a和b的并行加載不發(fā)生存儲(chǔ)體沖突。
- 沖突例子:a首地址=0;b首地址=8N
- 不沖突例子:a首地址=0;b首地址=8N+4
但是,完全控制數(shù)組和其它對(duì)象在存儲(chǔ)器空間的起始位置并不總是可以做到,尤其是當(dāng)一個(gè)指針作為參數(shù)傳遞給函數(shù)時(shí),調(diào)用這個(gè)函數(shù)的指針參數(shù)可能指向不同的存儲(chǔ)器位置。
如果不能知道數(shù)組a和b在存儲(chǔ)體中的排列信息,則只能肯定a[0-1]和a[2-3]不會(huì)發(fā)生存儲(chǔ)體沖突,同理于b[0-1]和b[2-3]。因此,可以通過(guò)循環(huán)展開的方式,安排a[0-1]和a[2-3]、b[0-1]和b[2-3]進(jìn)行同時(shí)存取,避免了可能的存儲(chǔ)體沖突。循環(huán)展開后的軟件流水核如下:
?
另外,在線性匯編中,可以通過(guò)“.mptr”偽指令,給編譯器提供數(shù)據(jù)的存儲(chǔ)相關(guān)信息,讓編譯器自動(dòng)分析是否會(huì)產(chǎn)生存儲(chǔ)體沖突并調(diào)整指令編排。
2. 存儲(chǔ)別名模糊(alias)
當(dāng)多個(gè)不同的變量名都指向相同的存儲(chǔ)區(qū)域,這時(shí)就發(fā)生別名模糊,也就是說(shuō),對(duì)這些變量進(jìn)行操作的指令可能存在存儲(chǔ)相關(guān)性。指令間的相關(guān)性限制了指令的編排,包括軟件流水編排。
匯編優(yōu)化器假定所有的存儲(chǔ)器引用,都是別名化的(aliased),它把控制權(quán)交給用戶,由用戶提供存儲(chǔ)是否別名的信息。編程者可通過(guò)一個(gè)編譯選項(xiàng)/“restrict”關(guān)鍵詞/兩條線性匯編偽指令來(lái)提供存儲(chǔ)別名的信息。
- -mt編譯選項(xiàng):表示代碼中沒有存儲(chǔ)別名現(xiàn)象。要仔細(xì)判斷是否能使用-mt,如果代碼使用了別名技術(shù)而又設(shè)置了-mt選項(xiàng),可能會(huì)出現(xiàn)意想不到的結(jié)果。
- restrict關(guān)鍵詞:在C編程中,對(duì)數(shù)組或指針變量用restrict進(jìn)行聲明,提示編譯器該變量指向的存儲(chǔ)區(qū)域不會(huì)與其它變量指向的存儲(chǔ)區(qū)域發(fā)生重疊。
- .mdep偽指令:用于明確聲明存儲(chǔ)器相關(guān)。
- .no_mdep偽指令:告訴匯編優(yōu)化器函數(shù)體中沒有存儲(chǔ)器相關(guān)性出現(xiàn)。
*不要把“存儲(chǔ)別名模糊(存儲(chǔ)器相關(guān))”和“存儲(chǔ)體沖突”兩個(gè)概念混淆,它們有著不同的含義和影響。別名模糊影響程序的正確性(當(dāng)然也可能影響性能),存儲(chǔ)體沖突影響程序的性能。存儲(chǔ)器相關(guān)對(duì)于指令編排影響大于存儲(chǔ)體沖突。
?
存儲(chǔ)器模式Vs數(shù)據(jù)終結(jié)方式[1,2]??
1. 存儲(chǔ)器模式
C6000編譯器支持兩種存儲(chǔ)器模式:小存儲(chǔ)器模式和大存儲(chǔ)器模式。
- 小存儲(chǔ)器模式:.bss段限制在32kB內(nèi),CPU可用直接尋址方式訪問(wèn).bss段中的所有對(duì)象而無(wú)需改變DP(B14)的值。
- 大存儲(chǔ)器模式:不限制.bss段大小,但CPU只能通過(guò)寄存器間接尋址訪問(wèn).bss中的數(shù)據(jù),也即需要先將對(duì)象地址讀入寄存器中,這帶來(lái)額外的操作。
當(dāng)全局/靜態(tài)變量(存放于.bss段)超過(guò)32kB,而又希望使用小存儲(chǔ)器模式獲得快的訪問(wèn)速度,有兩種解決辦法:
- 對(duì)于大的數(shù)組定義,使用far關(guān)鍵字,如此數(shù)據(jù)不占用.bss段空間,而放入.far段。
- 使用-ml/-ml0選項(xiàng),編譯器自動(dòng)對(duì)集合數(shù)據(jù)類型(如結(jié)構(gòu)體和數(shù)組)使用far存取。
?
2. 數(shù)據(jù)終結(jié)方式
指的是多字節(jié)數(shù)據(jù)內(nèi)部高低有效位的存放順序。C6000支持兩種終結(jié)方式:小端終結(jié)方式和大端終結(jié)方式。
- 小端終結(jié)(Little-Endian):數(shù)據(jù)高有效位字節(jié)存放在地址高位字節(jié)(高位高地址)。
- 大端終結(jié)(Big-Endian):數(shù)據(jù)高有效位字節(jié)存放在地址低位字節(jié)(高位低地址)。
?
內(nèi)存邊界對(duì)齊[1,3] ?
C67X DSP支持單次存/取16bit(半字)、32bit(字)、64bit(雙字)數(shù)據(jù),但前提是數(shù)據(jù)的存放分別滿足半字對(duì)齊、字對(duì)齊和雙字對(duì)齊。目前C64X支持在非對(duì)齊情況下單次存/取32bit和64bit寬度的數(shù)據(jù)。
所謂半字對(duì)齊指的是數(shù)據(jù)地址的最低1位為0;字對(duì)齊指的是數(shù)據(jù)地址的最低2位為0;雙字對(duì)齊指的是數(shù)據(jù)地址的最低3位為0。
對(duì)不支持非對(duì)齊單次存/取的器件來(lái)說(shuō),如果讓CPU用多字節(jié)存/取指令一次操作非對(duì)齊的數(shù)據(jù),將會(huì)產(chǎn)生額外操作,有些處理器甚至無(wú)法處理而產(chǎn)生錯(cuò)誤!下圖給出了一個(gè)處理示例,從圖中可見,原本單次可以完成的操作由于數(shù)據(jù)未能對(duì)齊而花費(fèi)了5次操作。
? ? ??
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖2 ?對(duì)非對(duì)齊數(shù)據(jù)進(jìn)行多字節(jié)訪問(wèn)
在C64X中,數(shù)組均默認(rèn)安排8字節(jié)(雙字)對(duì)齊;在C62X和C67X中,數(shù)組按4字節(jié)/8字節(jié)對(duì)齊。
結(jié)構(gòu)體的對(duì)齊方式由其最大數(shù)據(jù)類型成員決定,結(jié)構(gòu)體占用的存儲(chǔ)空間總是最大成員類型大小的倍數(shù)(注意并不是簡(jiǎn)單地乘以成員個(gè)數(shù))。如下兩個(gè)結(jié)構(gòu)體A和B,它們所占的存儲(chǔ)空間分別是8、12。
struct A
{
? ?short x;
??? short y;
??? int z;
};
?
struct B
{
??? short x;
??? int y;
??? short z;
};
數(shù)據(jù)集邊界對(duì)齊并不意味著它里面的每一個(gè)元素的地址都為對(duì)齊長(zhǎng)度的倍數(shù),而是保證數(shù)據(jù)集的起始地址和<結(jié)束地址+1>為對(duì)齊長(zhǎng)度的倍數(shù)。
對(duì)齊的存儲(chǔ)器訪問(wèn)
在C/C++代碼中,有三個(gè)pragma預(yù)編譯語(yǔ)句可以用來(lái)指示編譯器將具體的數(shù)據(jù)按指定的方式進(jìn)行對(duì)齊存儲(chǔ)。
- DATA_ALIGN:將數(shù)據(jù)進(jìn)行2的整數(shù)次冪對(duì)齊
- DATA_MEM_BANK:將數(shù)據(jù)對(duì)齊到指定的bank
- STRUCT_ALIGN(C特有):用于指定結(jié)構(gòu)體、聯(lián)合體進(jìn)行2的整數(shù)次冪對(duì)齊
使用_nassert()內(nèi)聯(lián)函數(shù)能夠指示編譯器某一數(shù)據(jù)的內(nèi)存對(duì)齊狀態(tài)。如
_nassert( ((int)sum & 0x3) == 0);
告訴編譯器sum為字邊界對(duì)齊,有了這個(gè)信息,編譯器就可以放心地安排SIMD(單指令多數(shù)據(jù))指令對(duì)數(shù)據(jù)進(jìn)行操作,但_nassert本身不產(chǎn)生任何操作。
可以使用_amemXX()和_amemXX_const()內(nèi)聯(lián)函數(shù)對(duì)對(duì)齊的字和半字進(jìn)行訪問(wèn)。一般這類內(nèi)存訪問(wèn)可以與_hi()、_lo()和_itod()等數(shù)據(jù)解包和打包內(nèi)聯(lián)函數(shù)聯(lián)合使用。
?
非對(duì)齊的存儲(chǔ)器訪問(wèn)
C64X支持非對(duì)齊的字和雙字訪問(wèn),其對(duì)邊界對(duì)齊和非邊界對(duì)齊數(shù)據(jù)訪問(wèn)的比較如下表所示:
?
從上表可以看出,C64X在每時(shí)鐘周期只能進(jìn)行一次非邊界對(duì)齊的存儲(chǔ)器訪問(wèn),因此,只要可能應(yīng)盡量使用邊界對(duì)齊的存儲(chǔ)器訪問(wèn)方式。
在C/C++代碼中,可以使用_memXX()和_memXX_const()內(nèi)聯(lián)函數(shù)對(duì)非對(duì)齊的字和半字進(jìn)行訪問(wèn)。一般這類內(nèi)存訪問(wèn)可以與_hi()、_lo()和_itod()等數(shù)據(jù)解包和打包內(nèi)聯(lián)函數(shù)聯(lián)合使用,下面是一個(gè)使用示例:
?
?
C6000 Cache(緩存)[4,5]??
為什么需要Cache?
大容量的存儲(chǔ)器(如DRAM)訪問(wèn)速度受到限制,一般比CPU時(shí)鐘速度慢很多;小容量的存儲(chǔ)器(如SRAM)能提供快速的訪問(wèn)速度。因此很多高性能的處理器都提供分層的存儲(chǔ)訪問(wèn)架構(gòu)。
如圖3所示,左右分別是平坦式存儲(chǔ)器架構(gòu)和2層cache的多層存儲(chǔ)架構(gòu)。在左邊的架構(gòu)中,即使CPU能運(yùn)行在600MHz,但由于片內(nèi)/片外存儲(chǔ)器只能運(yùn)行在300MHz/100MHz,CPU在訪問(wèn)存儲(chǔ)時(shí)需插入等待周期。
? ? ? ? ? ? ? ? ? ? ? ? ?圖3 ?平坦式和層級(jí)式存儲(chǔ)器架構(gòu)
Cache部分工作狀態(tài)說(shuō)明
- Cache hit(緩存命中):對(duì)于已經(jīng)緩存的程序/數(shù)據(jù),訪問(wèn)將引起緩存命中,緩存中的指令/數(shù)據(jù)立即送入CPU而無(wú)需等待。
- Cache miss(緩存缺失):發(fā)生缺失時(shí),首先通過(guò)EMIF讀入需要的指令/數(shù)據(jù),指令/數(shù)據(jù)在送入CPU的同時(shí)被存入Cache,讀入程序/數(shù)據(jù)的過(guò)程CPU被掛起。
- Cache flush(緩存命中):清空Cache已經(jīng)緩存的數(shù)據(jù)。
- Cache freeze(緩存凍結(jié)):Cache內(nèi)容不再改變,發(fā)生缺失時(shí),從EMIF中讀入的指令包不會(huì)同時(shí)存入Cache。
- Cache bypass(緩存旁路):Cache內(nèi)容不再改變,任何程序/數(shù)據(jù)都將從緩存外存儲(chǔ)器訪問(wèn)。
?
C6000的存儲(chǔ)架構(gòu)
C6000系列DSP在片內(nèi)RAM和CPU之間提供兩層Cache L1和L2,每層Cache又分為獨(dú)立的程序Cache和數(shù)據(jù)Cache。其中L1是固定的,L2可以被重映射為普通片內(nèi)RAM。
對(duì)程序/數(shù)據(jù)進(jìn)行訪問(wèn)時(shí),CPU首先到L1 Cache中尋找,命中則直接訪問(wèn),如果產(chǎn)生缺失,則繼續(xù)在L2Cache中尋找,如果還未命中,則到片內(nèi)RAM或片外RAM中尋址數(shù)據(jù)。
? ? ? ? ? ? ? ? ? ? ? ? ?圖4 ?C6000 CPU的程序/數(shù)據(jù)訪問(wèn)流程
訪問(wèn)定位的規(guī)律
由圖4可知,要保證CPU的存儲(chǔ)訪問(wèn)效率,只有在CPU在大部分的訪問(wèn)都是只針對(duì)最靠近它的存儲(chǔ)區(qū)時(shí)才有效。幸運(yùn)的是,根據(jù)訪問(wèn)定位的規(guī)律,這一條可以保證。訪問(wèn)的定位規(guī)律表明程序在一個(gè)相對(duì)小的時(shí)間窗口對(duì)僅需要一個(gè)相對(duì)較小size的數(shù)據(jù)和代碼。數(shù)據(jù)定位的兩條規(guī)律:
- 空間關(guān)聯(lián)性:當(dāng)一個(gè)數(shù)據(jù)被訪問(wèn)時(shí),它臨近的數(shù)據(jù)又很大可能會(huì)被后續(xù)的存儲(chǔ)訪問(wèn)。
- 時(shí)間關(guān)聯(lián)性:一個(gè)存儲(chǔ)區(qū)被訪問(wèn)時(shí),在下一個(gè)臨近的時(shí)間點(diǎn)還會(huì)被訪問(wèn)。
優(yōu)化cache性能
從訪問(wèn)定位規(guī)律出發(fā),可總結(jié)出優(yōu)化cache性能的一些基本準(zhǔn)則:
- 讓函數(shù)盡可能充分的對(duì)數(shù)據(jù)處理以提高數(shù)據(jù)的重用。
- 組織數(shù)據(jù)和代碼以提高cache命中率。
- 合理的空間劃分來(lái)平衡程序cache和數(shù)據(jù)cache。
- 組合那些對(duì)相同數(shù)據(jù)進(jìn)行處理的函數(shù)在一個(gè)存儲(chǔ)區(qū)域。
?
?段[1,6]??
目標(biāo)文件(.obj)的最小單位稱為段,它是占據(jù)一個(gè)連續(xù)空間的代碼塊或者數(shù)據(jù)塊。連接器的功能之一就是把段重定位到目標(biāo)系統(tǒng)的存儲(chǔ)器映像圖中。所有段都可以獨(dú)立重定位,用戶可以把任一段置入目標(biāo)存儲(chǔ)器任一指定塊內(nèi)。
一個(gè)COFF文件包含三個(gè)默認(rèn)段:.text、.data、.bss。用戶還可以創(chuàng)建、命名、連接自己的段,也可以繼續(xù)在各個(gè)段中繼續(xù)劃分子段。
在C/C++代碼中,有兩個(gè)預(yù)編譯語(yǔ)句可用來(lái)將特定的代碼或數(shù)據(jù)分配到指定的段中:
- CODE_SECTION:為代碼分配段。
- DATA_SECTION:為數(shù)據(jù)分配段。
?
棧和堆[1,6]
棧(.stack)和堆(.heap)是為處理器運(yùn)行時(shí)提供支持的兩個(gè)存儲(chǔ)區(qū)。
棧是由編譯器在需要時(shí)分配的,不需要時(shí)自動(dòng)清除的變量存儲(chǔ)區(qū)。它用于存放局部變量、函數(shù)參數(shù)等臨時(shí)數(shù)據(jù)。
堆用于動(dòng)態(tài)內(nèi)存分配。堆在內(nèi)存中位于bss區(qū)和棧區(qū)之間。一般由程序員分配和釋放,若程序員不釋放,程序結(jié)束時(shí)有可能由OS回收。例如C中常用的malloc()函數(shù)就是在堆中開辟區(qū)間存放數(shù)據(jù)。
?
參考文獻(xiàn)/資料??
【1】田黎育,何佩琨,朱夢(mèng)宇. TMS320C6000系列DSP編程工具與指南[M].北京:清華大學(xué)出版社 2006.
【2】董言治,婁樹理,劉松濤. TMS320C6000系列DSP系統(tǒng)結(jié)構(gòu)原理與應(yīng)用教程[M]. 清華大學(xué)出版社, 2014.
【3】Data alignment: Straighten up and fly right - IBM developerworks.
【4】Cache memory – Wikipedia.
【5】如何優(yōu)化使用C6000系列C64x的Cache--原理,Cache種類和優(yōu)化策略 - nouth的網(wǎng)易博客
【6】C語(yǔ)言中內(nèi)存分配 - youoran的CSDN專欄.
轉(zhuǎn)載于"http://www.cnblogs.com/ncdxlxk/p/9221116.html"
?
總結(jié)
以上是生活随笔為你收集整理的理解:TI C6000 数据存储处理与性能优化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 批处理bat命令--获取当前盘符和当前目
- 下一篇: exit()与_exit()的区别