添加到Lundin。是的,像ARM這樣的指令集可以讓你有一個基於寄存器的索引,或者有一些直接索引到達,你可能會受益於鼓勵編譯器使用索引。同樣,儘管ARM例如可以在加載指令中增加它的指針寄存器,但是在一個指令中基本上是* p ++。
它總是使用p [i]或p [i ++] vs * p或* p ++來折騰,有些指令集更明顯地採用哪條路徑。
同樣,你的索引。如果你沒有使用它而不是向上計數,則可以在每個循環中保存一條指令,也許更多。如果您在倒數雖然你可能會節省每循環的指令
inc reg
cmp reg,#7
bne loop_top
:
dec reg
bne loop_top
甚至一個處理器,我知道的
decrement_and_jump_if_not_zero loop_top
編譯器通常是一些可以這樣做知道這一點,你不必鼓勵他們。但是如果你使用內存讀取順序很重要的p [i]形式,那麼編譯器不能或至少不應該任意改變讀取順序。所以對於這種情況,你會希望代碼倒數。
所以我嘗試了所有的這些事情:
unsigned fun1 (const unsigned char *p, unsigned *x)
{
unsigned sum;
unsigned sqsum;
int i;
unsigned f;
sum = 0;
sqsum = 0;
for(i=0; i<8; i++)
{
f = *p++;
sum += f;
sqsum += f*f;
}
//to keep the compiler from optimizing
//stuff out
x[0]=sum;
return(sqsum);
}
unsigned fun2 (const unsigned char *p, unsigned *x )
{
unsigned sum;
unsigned sqsum;
int i;
unsigned f;
sum = 0;
sqsum = 0;
for(i=8;i--;)
{
f = *p++;
sum += f;
sqsum += f*f;
}
//to keep the compiler from optimizing
//stuff out
x[0]=sum;
return(sqsum);
}
unsigned fun3 (const unsigned char *p, unsigned *x)
{
unsigned sum;
unsigned sqsum;
int i;
sum = 0;
sqsum = 0;
for(i=0; i<8; i++)
{
sum += (unsigned)p[i];
sqsum += ((unsigned)p[i])*((unsigned)p[i]);
}
//to keep the compiler from optimizing
//stuff out
x[0]=sum;
return(sqsum);
}
unsigned fun4 (const unsigned char *p, unsigned *x)
{
unsigned sum;
unsigned sqsum;
int i;
sum = 0;
sqsum = 0;
for(i=8; i;i--)
{
sum += (unsigned)p[i-1];
sqsum += ((unsigned)p[i-1])*((unsigned)p[i-1]);
}
//to keep the compiler from optimizing
//stuff out
x[0]=sum;
return(sqsum);
}
與GCC和LLVM(鐺)。當然,因爲它是一個常量,所以展開循環。海灣合作委員會,每個實驗產生相同的代碼,在情況下微妙的寄存器組合改變。我會爭論一個錯誤,因爲其中至少有一個讀取不是按代碼描述的順序。
海灣合作委員會的所有四個解決方案是這樣的,有一些讀取重新排序,注意讀取順序從源代碼。如果這是針對依賴於代碼所描述的順序的硬件/邏輯,那麼您將遇到大問題。
00000000 <fun1>:
0: e92d05f0 push {r4, r5, r6, r7, r8, sl}
4: e5d06001 ldrb r6, [r0, #1]
8: e00a0696 mul sl, r6, r6
c: e4d07001 ldrb r7, [r0], #1
10: e02aa797 mla sl, r7, r7, sl
14: e5d05001 ldrb r5, [r0, #1]
18: e02aa595 mla sl, r5, r5, sl
1c: e5d04002 ldrb r4, [r0, #2]
20: e02aa494 mla sl, r4, r4, sl
24: e5d0c003 ldrb ip, [r0, #3]
28: e02aac9c mla sl, ip, ip, sl
2c: e5d02004 ldrb r2, [r0, #4]
30: e02aa292 mla sl, r2, r2, sl
34: e5d03005 ldrb r3, [r0, #5]
38: e02aa393 mla sl, r3, r3, sl
3c: e0876006 add r6, r7, r6
40: e0865005 add r5, r6, r5
44: e0854004 add r4, r5, r4
48: e5d00006 ldrb r0, [r0, #6]
4c: e084c00c add ip, r4, ip
50: e08c2002 add r2, ip, r2
54: e082c003 add ip, r2, r3
58: e023a090 mla r3, r0, r0, sl
5c: e080200c add r2, r0, ip
60: e5812000 str r2, [r1]
64: e1a00003 mov r0, r3
68: e8bd05f0 pop {r4, r5, r6, r7, r8, sl}
6c: e12fff1e bx lr
的指數爲負載和細微寄存器混合來自GCC的功能之間的唯一差別,所有的操作都是以相同的順序是相同的。
LLVM /鐺:
00000000 <fun1>:
0: e92d41f0 push {r4, r5, r6, r7, r8, lr}
4: e5d0e000 ldrb lr, [r0]
8: e5d0c001 ldrb ip, [r0, #1]
c: e5d03002 ldrb r3, [r0, #2]
10: e5d08003 ldrb r8, [r0, #3]
14: e5d04004 ldrb r4, [r0, #4]
18: e5d05005 ldrb r5, [r0, #5]
1c: e5d06006 ldrb r6, [r0, #6]
20: e5d07007 ldrb r7, [r0, #7]
24: e08c200e add r2, ip, lr
28: e0832002 add r2, r3, r2
2c: e0882002 add r2, r8, r2
30: e0842002 add r2, r4, r2
34: e0852002 add r2, r5, r2
38: e0862002 add r2, r6, r2
3c: e0870002 add r0, r7, r2
40: e5810000 str r0, [r1]
44: e0010e9e mul r1, lr, lr
48: e0201c9c mla r0, ip, ip, r1
4c: e0210393 mla r1, r3, r3, r0
50: e0201898 mla r0, r8, r8, r1
54: e0210494 mla r1, r4, r4, r0
58: e0201595 mla r0, r5, r5, r1
5c: e0210696 mla r1, r6, r6, r0
60: e0201797 mla r0, r7, r7, r1
64: e8bd41f0 pop {r4, r5, r6, r7, r8, lr}
68: e1a0f00e mov pc, lr
更容易閱讀和跟隨,也許想着緩存和獲取讀取所有在一杆。至少在一個案例中,llvm也得到了無序的讀取。
00000144 <fun4>:
144: e92d40f0 push {r4, r5, r6, r7, lr}
148: e5d0c007 ldrb ip, [r0, #7]
14c: e5d03006 ldrb r3, [r0, #6]
150: e5d02005 ldrb r2, [r0, #5]
154: e5d05004 ldrb r5, [r0, #4]
158: e5d0e000 ldrb lr, [r0]
15c: e5d04001 ldrb r4, [r0, #1]
160: e5d06002 ldrb r6, [r0, #2]
164: e5d00003 ldrb r0, [r0, #3]
是的,對於從ram平均一些值,順序不是問題,繼續前進。
所以編譯器選擇展開的路徑並且不關心微觀光學。由於循環的大小,每個循環都選擇刻錄一堆保存一個加載值的寄存器,然後從這些臨時讀取或乘法中執行加法操作。如果我們稍微增加循環的大小,我希望看到展開循環內的sum和sqsum累積,因爲它在寄存器中用完,或者在他們選擇不展開循環的位置達到閾值。
如果我將長度傳入,並將上面的代碼中的8代入長度傳遞,強制編譯器將此循環寫出。你看八九不離十了優化,這樣的指令用於:
a4: e4d35001 ldrb r5, [r3], #1
而且是手臂他們做環寄存器的在一個地方的指令的修改和分支,如果不相等的一些後來......因爲他們可以。
授予這是一個數學函數,但使用浮動是痛苦的。而使用多層是痛苦的,分隔更糟糕,幸運的是使用了換擋。幸運的是,這是沒有簽名的,所以你可以使用shift(編譯器應該知道使用算術移位,如果可用的話,如果你對有符號數字使用了除法)。因此,基本上集中在內部循環的微觀優化,因爲它可以被多次運行,並且如果這可以被改變,所以如果可能的話,它將變成移位並添加,或者排列數據,以便您可以將它從循環(如果可能,不要在別處浪費其他拷貝循環來做到這一點)
const unsigned char* p = (const unsigned char*)(j*patch->step + aux);
你可以得到一些速度。我沒有嘗試它,但因爲它是一個循環中的循環編譯器可能不會展開該循環...
長話短說,你可能會得到一些收益取決於設置對一個笨的編譯器指令,但是這個代碼是不是真的不好使編譯器可以優化它,以及你可以。
有很多事情可以影響性能(假設您正在測量處理時間的方式)。顯然,你正在使用OpenCV的,所以我會說,圖像的尺寸使得很大的差異。它有多大? – karlphillip 2011-12-15 11:45:38