2016-05-30 1 views
4

私は10,000の要素を600,000回追加するループ(宿題として)を最適化しています。最適化が行われない時間は23.34秒〜、私の目標はBの場合は7秒未満、Aの場合は5秒未満に達することです。ループ分割はコードを遅くします

このように、最初に最適化を開始しました。

これにより、実行時間が約6.4秒に短縮されます(さらに展開すると約6になります)。

私は、読み書きの依存関係の時間を節約するために、サブサムの追加と最終的な合計を試してみることにしました。このようなコードが思いつきました。

int  j; 

    for (j = 0; j < ARRAY_SIZE; j += 8) { 
     sum0 += array[j] + array[j+1]; 
     sum1 += array[j+2] + array[j+3]; 
     sum2 += array[j+4] + array[j+5]; 
     sum3 += array[j+6] + array[j+7]; 

は、しかし約6.8秒

にこの増加ランタイムは、私は、ポインタを使用して、同様の手法を試してみましたが、私は何ができる最高は約15秒でした。

これは私が稼働しているマシン(学校が購入したサービスなので)は、Red Hatを実行していると思われる32ビットのリモートIntelベースのLinux仮想サーバーです。

私はコードを高速化するために考えられるすべての手法を試しましたが、それらはすべて逆の効果を持つようです。誰かが私が間違っていることを詳しく説明できますか?または、ランタイムを低下させるために使用できる別のテクニックですか?先生ができる最高のものは約4.8秒でした。

追加の条件として、完成したプロジェクトに50行以上のコードを入れることはできません。そのため、複雑な作業は不可能です。ここで

#include <stdio.h> 
#include <stdlib.h> 

// You are only allowed to make changes to this code as specified by the comments in it. 

// The code you submit must have these two values. 
#define N_TIMES  600000 
#define ARRAY_SIZE 10000 

int main(void) 
{ 
    double *array = calloc(ARRAY_SIZE, sizeof(double)); 
    double sum = 0; 
    int  i; 

    // You can add variables between this comment ... 

// double sum0 = 0; 
// double sum1 = 0; 
// double sum2 = 0; 
// double sum3 = 0; 

    // ... and this one. 

    // Please change 'your name' to your actual name. 
    printf("CS201 - Asgmt 4 - ACTUAL NAME\n"); 

    for (i = 0; i < N_TIMES; i++) { 

     // You can change anything between this comment ... 

     int  j; 

     for (j = 0; j < ARRAY_SIZE; j += 8) { 
      sum += array[j] + array[j+1] + array[j+2] + array[j+3] + array[j+4] + array[j+5] + array[j+6] + array[j+7]; 
     } 

     // ... and this one. But your inner loop must do the same 
     // number of additions as this one does. 

     } 

    // You can add some final code between this comment ... 
// sum = sum0 + sum1 + sum2 + sum3; 
    // ... and this one. 

    return 0; 
} 

両方のソースの完全なコピーである砕きコード

#include <stdio.h> 
#include <stdlib.h> 

// You are only allowed to make changes to this code as specified by the comments in it. 

// The code you submit must have these two values. 
#define N_TIMES  600000 
#define ARRAY_SIZE 10000 

int main(void) 
{ 
    double *array = calloc(ARRAY_SIZE, sizeof(double)); 
    double sum = 0; 
    int  i; 

    // You can add variables between this comment ... 

    double sum0 = 0; 
    double sum1 = 0; 
    double sum2 = 0; 
    double sum3 = 0; 

    // ... and this one. 

    // Please change 'your name' to your actual name. 
    printf("CS201 - Asgmt 4 - ACTUAL NAME\n"); 

    for (i = 0; i < N_TIMES; i++) { 

     // You can change anything between this comment ... 

     int  j; 

     for (j = 0; j < ARRAY_SIZE; j += 8) { 
      sum0 += array[j] + array[j+1]; 
      sum1 += array[j+2] + array[j+3]; 
      sum2 += array[j+4] + array[j+5]; 
      sum3 += array[j+6] + array[j+7]; 
     } 

     // ... and this one. But your inner loop must do the same 
     // number of additions as this one does. 

     } 

    // You can add some final code between this comment ... 
    sum = sum0 + sum1 + sum2 + sum3; 
    // ... and this one. 

    return 0; 
} 

答えは、我々はグレードを判断するために使用する「時間」アプリケーションが少しありますビットオフ。私ができる最善のことは、ループを50回展開し、TomKarzesの基本フォーマットを使って以下のようにグループ化することで4.9〜でした。

int  j; 
     for (j = 0; j < ARRAY_SIZE; j += 50) { 
      sum +=(((((((array[j] + array[j+1]) + (array[j+2] + array[j+3])) + 
        ((array[j+4] + array[j+5]) + (array[j+6] + array[j+7]))) + 
        (((array[j+8] + array[j+9]) + (array[j+10] + array[j+11])) + 
        ((array[j+12] + array[j+13]) + (array[j+14] + array[j+15])))) + 
        ((((array[j+16] + array[j+17]) + (array[j+18] + array[j+19]))))) + 
        (((((array[j+20] + array[j+21]) + (array[j+22] + array[j+23])) + 
        ((array[j+24] + array[j+25]) + (array[j+26] + array[j+27]))) + 
        (((array[j+28] + array[j+29]) + (array[j+30] + array[j+31])) + 
        ((array[j+32] + array[j+33]) + (array[j+34] + array[j+35])))) + 
        ((((array[j+36] + array[j+37]) + (array[j+38] + array[j+39])))))) + 
        ((((array[j+40] + array[j+41]) + (array[j+42] + array[j+43])) + 
        ((array[j+44] + array[j+45]) + (array[j+46] + array[j+47]))) + 
        (array[j+48] + array[j+49]))); 
     } 
+0

「B」と「A」とは何ですか? – pvg

+0

これは意味をなさない。パフォーマンスに影響を与えてもそれほど劇的ではないだろう。 –

+1

これは、現代のCPUがベクトル操作を最適化する方法に起因する可能性があります。 – Will

答えて

2

私はグループ分けで少し実験しました。私のマシンでは、私のgccで、私は以下のが一番働いたことがわかった。つまり

for (j = 0; j < ARRAY_SIZE; j += 16) { 
     sum = sum + 
       (array[j ] + array[j+ 1]) + 
       (array[j+ 2] + array[j+ 3]) + 
       (array[j+ 4] + array[j+ 5]) + 
       (array[j+ 6] + array[j+ 7]) + 
       (array[j+ 8] + array[j+ 9]) + 
       (array[j+10] + array[j+11]) + 
       (array[j+12] + array[j+13]) + 
       (array[j+14] + array[j+15]); 
    } 

を、それはそれはペアにグループ合計を、16回アンロールし、それが直線的にペアを追加しています。私も+=演算子を削除しました。これは、加算で初めてsumが使用されたときに影響します。

何か変更を加えなくても、測定された時間は実行ごとに大きく異なることがわかったので、時間が改善されたか悪化したかについて何らかの結論を出す前に、

このバージョンの内部ループでは、マシン上でどのような数字が得られるかを知りたいです。

更新:

int  j1, j2; 

    j1 = 0; 
    do { 
     j2 = j1 + 20; 
     sum = sum + 
       (array[j1 ] + array[j1+ 1]) + 
       (array[j1+ 2] + array[j1+ 3]) + 
       (array[j1+ 4] + array[j1+ 5]) + 
       (array[j1+ 6] + array[j1+ 7]) + 
       (array[j1+ 8] + array[j1+ 9]) + 
       (array[j1+10] + array[j1+11]) + 
       (array[j1+12] + array[j1+13]) + 
       (array[j1+14] + array[j1+15]) + 
       (array[j1+16] + array[j1+17]) + 
       (array[j1+18] + array[j1+19]); 
     j1 = j2 + 20; 
     sum = sum + 
       (array[j2 ] + array[j2+ 1]) + 
       (array[j2+ 2] + array[j2+ 3]) + 
       (array[j2+ 4] + array[j2+ 5]) + 
       (array[j2+ 6] + array[j2+ 7]) + 
       (array[j2+ 8] + array[j2+ 9]) + 
       (array[j2+10] + array[j2+11]) + 
       (array[j2+12] + array[j2+13]) + 
       (array[j2+14] + array[j2+15]) + 
       (array[j2+16] + array[j2+17]) + 
       (array[j2+18] + array[j2+19]); 
    } 
    while (j1 < ARRAY_SIZE); 

これはある交互誘導変数で、20の二つのグループに分け、40の合計アンロール量を使用しています。ここに(私のコンパイラと私のマシン上で、)私の現在の最速バージョンです依存関係を解消するために事前にインクリメントされたもの、そしてテスト後のループです。繰り返しますが、カッコをグループ化してコンパイラとプラットフォーム用に微調整することができます。

+0

私はあなたのコードを(異なるグループ化を使ったバージョンとともに)走らせ、テストしたすべてのバージョン(それを含む)の平均で約5.5秒を得ました。私の現在のバージョンは20に展開され、そのような隣人とそれらの隣人とのグループ化が繰り返し行われ、約5.4-5.1になるまで繰り返されます。 –

+0

できるだけたくさん絞っているように聞こえます。アンローリング量は20です。通常、私は2の勢力で展開することを考えていますが、この場合は、合計旅行回数の要因である他の金額で展開しない理由はありません。 –

+0

私はあなたの先生が何をして信頼できるように5歳以下になったのか疑問に思い始めました。わずかに異なるプラットフォーム上のgccのわずかに異なるバージョンのコードジェンを推測しているようです。 – pvg

-1

私は、次のアプローチを使用してコードを試してみました:

  • 最適化なし、1、簡単なsum +=による整数インデックスを持つforループ。これは私のの64ビット 2011 MacBook Proで16.4秒かかりました。

  • gcc -O2、同じコード、5.46秒に下がった。

  • gcc -O3、同じコード、5.45秒に下がった。

  • あなたのコードを8変数加算でsum変数に使ってみました。これは2.03秒に短縮されました。

  • これを16倍加算して合計変数にすると、1.91秒に短縮されました。

  • 私は合計変数に32ウェイ加算を倍増しました。 WENT UPから2.08秒までの時間。

  • @kcraigieのように、私はポインタアプローチに切り替えました。 -O3の場合、時間は6.01秒でした。 (私には非常に驚くべきこと!)

    register double * p; 
    for (p = array; p < array + ARRAY_SIZE; ++p) { 
        sum += *p; 
    } 
    
  • 私はsum += *p++で、whileループにループに変更し、ダウン5.64秒までの時間を得ました。

  • whileループをupの代わりにカウントダウンするように変更しました。時間は5.88秒になりました。

  • 私は8進整数インデックスを持つforループに戻し、8レジスタのdouble sum [0-7]変数を追加し、[0,7]のNのsumNに_array [j + N]を追加しました]。 _arrayがdouble32 * constとして登録されていると、arrayに初期化され、重要である可能性があります。これは1.86秒にダウンタイムを持っていた。

  • + _array [n]の10,000個のコピーに拡張されたマクロに変更され、Nは定数です。その後、sum = tnKX(addsum)を実行し、コンパイラがセグメンテーションフォルトでクラッシュしました。したがって、純粋なインライン展開のアプローチは機能しません。

  • マクロを、N定数でsum += _array[n]の10,000コピーに拡大しました。それは6.63秒で走った!!コードをすべて読み込むオーバーヘッドが、インライン展開の効率を低下させることは明らかです。

  • static double _array[ARRAY_SIZE];を宣言してから、__builtin_memcpyを使用して、最初のループの前にコピーしてみました。 8方向並列加算では、これは2.96秒の時間となった。私は静的な配列が行く方法だとは思わない。 (悲しい - 。私は一定のアドレスが勝者になります期待していた)

を全てこのことから、16ウェイのインライン化や並列の変数があるべき8ウェイ移動するための方法のように思えます。あなたは自分のプラットフォームでこれを試してみる必要があります - 私はより広いアーキテクチャが数字に何をするのか分かりません。

編集:@pvgからの提案に続き

、私はこのコードを追加:< 0.01秒に実行時間を削減

int ntimes = 0; 

// ... and this one. 
... 
    // You can change anything between this comment ... 

      if (ntimes++ == 0) { 

。 ;-) F-スティックでヒットしなければ勝者です。

+0

これは、コメントに記載されているとおり、これは厳密に-O0割り当てです。 – pvg

+0

私がこの投稿を開始したとき、それはまだ言われていませんでした。 ;-) –

+0

私の最後のテストは、16ウェイの追加が現時点で最善の結果であるように思えます。私はそれをプッシュする必要があります。 –

関連する問題