[英]Compiler optimization of if statement in C
我在C中有一個像這樣的函數(用偽ish代碼,刪除不重要的部分):
int func(int s, int x, int* a, int* r) {
int i;
// do some stuff
for (i=0;i<a_really_big_int;++i) {
if (s) r[i] = x ^ i;
else r[i] = x ^ a[i];
// and maybe a couple other ways of computing r
// that are equally fast individually
}
// do some other stuff
}
如此多的代碼被調用,以至於該循環實際上是代碼中的速度瓶頸。 我想知道幾件事:
由於開關s
是在功能恆定,將良好的編譯器優化循環,使該分支沒有放緩下來所有的時間?
如果不是,什么是優化此代碼的好方法?
====
這是更新的完整示例:
int func(int s,
int start,int stop,int stride,
double *x,double *b,
int *a,int *flips,int *signs,int i_max,
double *c)
{
int i,k,st;
for (k=start; k<stop; k += stride) {
b[k] = 0;
for (i=0;i<i_max;++i) {
/* this is the code in question */
if (s) st = k^flips[i];
else st = a[k]^flips[i];
/* done with code in question */
b[k] += x[st] * (__builtin_popcount(st & signs[i])%2 ? -c[i] : c[i]);
}
}
}
編輯2:
萬一有人好奇,我最終重構了代碼,並在外部提升了整個內部for循環(使用i_max
),從而使really_big_int
循環更加簡單,並且希望可以輕松地向量化! (並且還要避免無數次地執行大量額外的邏輯)
優化代碼的一種顯而易見的方法是將條件拉到循環之外:
if (s)
for (i=0;i<a_really_big_int;++i) {
r[i] = x ^ i;
}
else
for (i=0;i<a_really_big_int;++i) {
r[i] = x ^ a[i];
}
精明的編譯器也許能夠一次將其更改為多個元素的r []分配。
微觀優化
通常他們不值得花時間-審查更大的問題更有效。
但是,要進行微優化,嘗試各種方法,然后對它們進行概要分析以找到最佳方法,則可以進行適度的改進。
除了@wallyk和@kabanus好的答案之外,一些簡單的編譯器還受益於以0結尾的循環。
// for (i=0;i<a_really_big_int;++i) {
for (i=a_really_big_int; --i; ) {
[編輯第二優化]
OP添加了一個更具競爭力的示例。 問題之一是編譯器無法假設b
和其他指針指向的內存不重疊。 這會阻止某些優化。
假設它們實際上不重疊,請對b
使用restrict
以允許優化。 const
對於較弱的編譯器也沒有幫助。 如果參考數據不重疊,那么對其他對象的restrict
也可能有益。
// int func(int s, int start, int stop, int stride, double *x,
// double *b, int *a, int *flips,
// int *signs, int i_max, double *c) {
int func(int s, int start, int stop, int stride, const double * restrict x,
double * restrict b, const int * restrict a, const int * restrict flips,
const int * restrict signs, int i_max, double *c) {
您的所有命令都是循環中的快速O(1)命令。 if
您的所有命令均為r[i]=somethingquick
形式, if
絕對是經過優化的,因此for + if也是如此。 大int可以有多小,這個問題可能會幫您解決?
從INT_MIN
到INT_MAX
求和成一個長變量的快速int main
在Windows的Ubuntu子系統上對我來說大約需要10秒鍾。 您的命令可能會將其乘以幾,這很快就會變成一分鍾。 底線是,如果您真的要重復很多,這可能是無法避免的。
如果r[i]
是獨立計算的,這將是線程/多處理的經典用法。
編輯:
我認為%
仍然是由編譯器優化的,但如果沒有優化,請注意x & 1
對於奇/偶校驗要快得多。
假設x86_64,您可以確保指針對齊到16個字節並使用內在函數 。 如果它僅在具有AVX2的系統上運行,則可以使用__mm256變體(類似於avx512 *)
int func(int s, int x, const __m128i* restrict a, __m128i* restrict r) {
size_t i = 0, max = a_really_big_int / 4;
__m128i xv = _mm_set1_epi32(x);
// do some stuff
if (s) {
__m128i iv = _mm_set_epi32(3,2,1,0); //or is it 0,1,2,3?
__m128i four = _mm_set1_epi32(4);
for ( ;i<max; ++i, iv=_mm_add_epi32(iv,four)) {
r[i] = _mm_xor_si128(xv,iv);
}
}else{ /*not (s)*/
for (;i<max;++i){
r[i] = _mm_xor_si128(xv,a[i]);
}
}
// do some other stuff
}
盡管if
語句將在任何不錯的編譯器上進行優化(除非您要求編譯器不對其進行優化),但我還是考慮在其中編寫優化(以防萬一您不進行優化而進行編譯)。
另外,盡管編譯器可能會優化“ absolute” if
語句,但我會考慮使用任何可用的內置函數或使用按位運算來手動優化它。
即
b[k] += x[st] *
( ((__builtin_popcount(st & signs[I]) & 1) *
((int)0xFFFFFFFFFFFFFFFF)) ^c[I] );
這將使用popcount
的最后一位(1 ==奇數,0 ==偶數),將其乘以const(如果為奇數,則所有位為1;如果為true,則所有位為0),然后將c[I]
值(即與0-c[I]
或~(c[I])
。
在第二個absolute
if語句未優化的情況下,這將避免指令跳轉。
聚苯乙烯
我使用了一個8字節長的值,並通過將其強制轉換為int
來將其長度截斷。 這是因為我不知道一個int
在您的系統上可能有多長時間(我的4個字節,即0xFFFFFFFF
)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.