簡體   English   中英

C中if語句的編譯器優化

[英]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

}

如此多的代碼被調用,以至於該循環實際上是代碼中的速度瓶頸。 我想知道幾件事:

  1. 由於開關s是在功能恆定,將良好的編譯器優化循環,使該分支沒有放緩下來所有的時間?

  2. 如果不是,什么是優化此代碼的好方法?

====

這是更新的完整示例:

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_MININT_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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM