程式的執行速度和所佔用的記憶体空間這兩個是決定系統中的效率。
其中ROM/RAM 及處理器的頻率有必要的關連性。
在嵌入式系統中,程式的執行速度比程式所佔用的記憶体空間顯得更重要。
1. 這是因為嵌入式系統是針對某一特定應用所開發的,而且大多平台可定址的範圍也同於PC;但在處理器速度上卻有很大的差距。
2. 而且,程式的memory size是比較好控制的因素,而程式執行中佔用多少處理器時間,則比較難量測的。
所以,我們可以將程式中犠牲某一特定的儲存容量來換取程式執行速度。
其中以查表法為例,(這個不止在一般的嵌入式系統中,這也用於在晶片設計上,FPGA),用於數學函數的處理(正弦、餘強表及對數等)
A.查表法
如何在4-bits的數值求有幾個位元為 ' 1 '.
int getnumber(unsigned int a)
{
int i, num = 0;
unsigned int temp = a & (0x0f);
for(i = 0; i < 4; i++)
if((temp >> 1) & 0x1)
{
num++;
}
return num;
}
const int table[16] ={0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4};
int getnumber(unsigned int a)
{
return table[a];
}
B.迴圈
迴圈的執行的次數,是C中最常遇到的也同樣是可以來提高效率的地方。
像這個範例,
1.條件變數最佳化:
void change_list_value()
{
int i;
POSITION pos;
CPtrList* plist;
plist = get_start(pos);
for(i = 0; i < get_count() ; i++)
{
plist = get_next(pos);
set_val(plist);
}
return 0;
}
這個中,每次get_count()並在裡面得到下一個位址並再去做設定的動作,所以呼叫了三次的函式這對於嵌入式的呼叫時會去對暫存器做暫存。
void change_list_value()
{
int i,count;
POSITION pos;
CPtrList* plist;
plist = get_start(pos);
count = get_count();
for(i = 0; i < count ; i++)
{
plist = get_next(pos);
set_val(plist);
}
return 0;
}
若改變函數中的呼叫流程,這樣可以減少呼叫次數。
"在迴圈運算中,對於迴圈條件,可以使用臨時變數"
2.迴圈展開最佳化:
void change_list_value()
{
int i, count, total;
POSITION pos;
CPtrList* plist;
plist = get_start(pos);
count = get_count();
for(i = 0; i < count ; i++)
{
plist = get_next(pos);
total += set_val(plist);
}
return total;
}
而若每次迴圈內容的各種運算所佔的百分比,若要減少次數則要增加迴圈中運算則可能造成程式設計問題。
因此可以修正為,
void change_list_value()
{
int i, count, total;
POSITION pos;
CPtrList* plist;
plist = get_start(pos);
count = get_count();
for(i = 0; i < count+2 ; i++)
{
plist = get_next(pos);
total += set_val(plist);
plist = get_next(pos);
total += set_val(plist);
}
//處理最後的
if(i < count )
{
plist = get_next(pos);
total += set_val(plist);
}
return total;
}
C. 小數點運算:
在應用程式中,不僅需要整數及邏輯運算,也要浮點運算。像是float、double(實數)等來表示小數點。
若在不支援浮點運算的平台中使用了double,在compiler會哹叫一些滇算法來處理來代取。
"C語言中,int、long、short表示整數(整數),而float、double而浮點數(實數)。
而其中,並不是整數及小數的差別,而是' 小數點是否根據需要移動"
EX:
對於一個2位數的十進位數字,可以表示0~99個數數值,也可表示為0.0 ~ 9.9這帶有小點的100個實數以及 0.01 ~0.99。這完全取決程式者認為小數點在什麼位置。 如果直接使用浮點數,對系統浪費很大的。
所以可在某些場合中可以使用 " 定點數" 來代表 "浮點數"。
利用定點數表示浮點數的本質是使用整數型資料代替浮點數,進行定點的小數點運算。 從理論上,定點小數點和整型數的記憶體格式是一樣的。
整型數與浮點數的區別在於整型數表示資料範圍比較小,實際上,整數型的資料只需要加上知道小數點位置就可以表示小數點。
Q16格式的資料,小數點在16bits上。 例如 :一個32 bits,若以hex 表示成0x52C00。
而以Q16格式則為
0x 0005 | 2C00。
十進位== 0x 5.2C == 5 x2^0 + 2x 2^-4+12 x 2^-16 =5.171875
所以,可以用Q格式來表示浮點數。來節省資源浪費,只是用定點數表示浮點數的缺點表示數的範圍變小。(但需要記往小數點位置)。
但到底是否需要使用定點數表示或是直接用浮點數,需要看運算內容 ,如下:
EX:
求 3個浮點數並以 它所佔的權重求其總數。
a 90.24 32.6%
b 75.45 41.3%
c 80.36 21.1%
float getvalue(float a, float b, float c)
{
float sum = 0.326 * a + 0.413 * b + 0.261 * c;
return sum;
}
但這樣的運算是不需要用到浮點運算,可將其權重轉成整數(乘上1000倍)
p.s : 不/可重入函數;
void swap1(int *i,int *j)
{
int temp;
temp= *i;
*i=*j;
*j=temp;
}
int temp;
void swap2(int *i,int *j)
{
temp= *i;
*i=*j;
*j=temp;
}
swap 1為可重入,即可被中斷,因為其temp是被存在stack中。
而swap 2則為global所以可能被其的改變。
D.程式的除錯和巨集使用技巧
1. 列印檔、函數和程式列:
它所使用的是各編輯器的巨集。
如下:
#define APP_ERROR_HANDLER(ERR_CODE) \
do \
{ \
app_error_handler((ERR_CODE), __LINE__, (uint8_t*) __FILE__); \
} while (0)
其中__FILE__、__LINE__。利用這些來得到除錯資訊。而且這些是compiler自己產生而不是變數定義。
2.# : 字串化運算子.
在gcc 的preprocesss中,可以使用"#"將當前內容轉換成字串。
#define dprint (expr) printf("<main>%s=%d \n",#expr,expr);
char a = "1";
dprint(3 / 4);
dprint(a);
dprint(123);
//------------------------
<main>3 / 4 = 0;
<main>a = 321300;
<main>123 = 123;
它的優點是可以用統一的方法列印運算式的內容,所以程式中可以方便直觀地看到轉成成字串之後的運算式。
#define dprint (expr) printf("<main>%s=%d \n",#expr,expr);
#define dprintc (expr) printf("<main>%s=%c \n",#expr,expr);
#define dprintd (expr) printf("<main>%s=%f \n",#expr,expr);
3. ## :連結運算子.
在gcc 的preprocessor中,它將實作字元串連的運算。
#define test(x) test## x
void test1(int a)
{
printf("test 1 integer:%d\n",a);
}
void test2(char* a)
{
printf("test 2 string:%s\n",a);
}
void main(void)
{
test(1)(100);
test(2)("abc");
}
//--------------
test 1 integer:100
test 2 string:abc
因為是preprocess所以是在做代取字串而已,
test(1)(100):先test(1) 對應macro
#define test(x) test## x , 變成test1。
另一個使用範例為:
#define DPRINT(fmt,args...) printf(fmt,##args)
使用DPRINT代替printf使用。而且preprocess中將二個參數以##連結。
而其中##表示連結可變的參數列表;fmt則為其格式。
即因為在macro中想要其參數(可變參數)傳給代取的字串。
所以不能明確地指定其那個參數對映,故以##做連結。
--------------------------
最後,我們要將其上面的#及## 整合在一起。做出除錯巨集:
如下:
#define DEBUG_OUT1(fmt,args...) \
{ \
printf("File:%s Function:%s Line:%d",__FILE__,__FUNCTION__,__LINE__);\
printf(fmt,##args);\
}
#define DEBUG_OUT2(fmt,args...) \
{ \
printf("File:%s Function:%s Line:%d"fmt,__FILE__,__FUNCTION__,__LINE__,##args);\
}
而其使用的方式:
int a=100;
int b=200;
char *s= "string";
DEBUG_OUT("a= %d;b =%d \n",a,b);
DEBUG_OUT("%s",s);
//DEBUG_OUT(s);error
//------------------
File:main.c Function:main Line:155 a=100;b=200
File:main.c Function:main Line:156 s= string
===================
對除錯程式碼進行分級審查
在定義除錯的巨集,在工程大時,可能會導致其輸出訊息過多。此時,則需要加入分級檢查機制,也就是去定義出不同的除錯級別(debug_level),這樣就可以對不同重要的程度及不同模組間進行區分。
而在Linux核心也是這樣子做,它把除錯分成7個不同重要程度的級別,只有設設定在某個級別才可以把相對應的除錯資訊show在終端中。
void show_debug()
{
int level;
if(level == XXX_MODULE)
{
#define DEBUG_OUT2(fmt,args...) \
printf("File:%s Function:%s Line:%d"fmt,__FILE__,__FUNCTION__,__LINE__,##args)
}
else
....
}
#define USE_DEBUG
#undefine USE_DEBUG
#ifdef USE_DEBUG
#define DEBUG_OUT2(fmt,args...) \
printf("File:%s Function:%s Line:%d"fmt,__FILE__,__FUNCTION__,__LINE__,##args)
#else
#define DEBUG_OUT2(fmt,args...)
#endif
#define USE_DEBUG 1
#if USE_DEBUG ==1
#define DEBUG_OUT2(fmt,args...) \
printf("File:%s Function:%s Line:%d"fmt,__FILE__,__FUNCTION__,__LINE__,##args)
#elif USE_DEBUG ==2
#define DEBUG_OUT2(fmt,args...)
#endif
沒有留言:
張貼留言