2

いくつかの計算のテストとタイミング調整を行っていました (プロセッサ上の 4 つのスレッドすべてと並列に実行すると 4 倍速く実行される for ループを見つけようとしていました)。並列化しました。25% の CPU 使用率でのみ実行されます。私のプロセッサの各コアには、スタックに割り当てられた C スタイルの配列である arr4 の独自のコピーがあり、各コアはそのスタック配列の各値を繰り返し変更することになっていました。最後に、タイマーが秒単位でかかった時間を出力します。並列化の時間が 40 秒かかる場合、並列化のない for ループの時間が 4*40 秒弱、つまり 160 秒になるようにしたいと考えています。最適化は最大速度に設定され、物理メモリのスタック サイズは 8 億バイトに設定されます (スタック オーバーフローを防ぐため)。ともかく、

#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <malloc.h>

int main (void)
{
    clock_t begin, end;
    double time_spent;

    begin = clock();
    {
        //int j;

        #pragma loop(hint_parallel(4))
        #pragma loop(ivdep)
        for (int j=0; j < 8; ++j)
        {
            int * __restrict const arr4 = (int *) _alloca(16000000*sizeof(int));

            for (int z = 0; z < 16000000; ++z)
            {
                arr4[z] = z;
            }

            #pragma loop(no_vector)
            for (int i = 0; i < 16000000; ++i)
            {
                for (int k = 0; k < 160; ++k)
                {
                    arr4[i] -= (7 - arr4[i] * 6 % (i+77) + 5 * 4 / 3 + 3 % 2 + 1 - (i+7));
                    arr4[i] += ((77 - 2 - (i+9)/2 + arr4[i]));
                    arr4[i] *= (8 - 2 + 6 % 3 / 2 + (i+6));
                }
            }
            printf(" %i ", arr4[((j+1)*666)%16]);
        }
    }
    end = clock();
    time_spent = (double)(end - begin) / ((double)CLOCKS_PER_SEC);
    printf("Test1: time as a floating point type is %f \n", time_spent);
    return 0;
}

この改訂された例でも、同じ 25% の CPU の問題が発生します。

#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <malloc.h>

int main (void)
{
clock_t begin, end;
double time_spent;

begin = clock();
int * __restrict const arr4 = (int *) _alloca(16000000*sizeof(int));

#pragma loop(hint_parallel(4))
#pragma loop(ivdep)
for (int j=0; j < 8; ++j)
{
    for (int i = 0; i < 16000000; ++i)
    {
       int v = i;    // eliminate initialization pass (z loop)
       for (int k = 0; k < 160; ++k)
       {
           v -= (7 - v * 6 % (i+77) + 5 * 4 / 3 + 3 % 2 + 1 - (i+7));
           v += ((77 - 2 - (i+9)/2 + v));
           v *= (8 - 2 + 6 % 3 / 2 + (i+6));
       }
       arr4[i] = v;
    }
    //printf(" %i ", arr4[((j+1)*666)%16]);
}

end = clock();
//time_spent = (double)(end - begin) / ((double)CLOCKS_PER_SEC);
time_spent = (double)(end - begin);
printf(" %i ", arr4[666]);
printf("Test1: time as a floating point type is %f \n", time_spent);
return 0;
}
4

1 に答える 1

4

まず第一に、プロセッサを追加しても、線形速度の向上は期待できません。通常、使用可能なコア数を 2 倍にしても、理想的な条件下では実行が約 1.8 倍向上するだけです。

人数で考えてみてください。10 人の開発チームを倍の 20 人にすると、自動的に 2 倍の仕事ができるようになりますか? いいえ、参加者の数が増えるにつれて、コミュニケーションと調整がより大きなタスクになるためです。

2 つ目は、タイマー ループ内で多くの非計算処理が行われていることです。外側のループにメモリ割り当てと printf があり、内側のループに複数のメモリの読み取りと書き込みがあります。特に、メモリアドレスからの読み取り、書き込み、再度読み取りなどを行っていると、レジスタ変数コンパイラの最適化が無効になる場合があります。

CPU がメモリの読み取りと書き込みの完了を待つために多くの時間を費やしている可能性があります。

配列内のデータの変更は外部オブザーバーには見えないように見えるため、arr4[i] 値をローカル int 変数にプルし、そのローカル int 変数ですべての操作を実行してから、ローカル int を記述することを検討する必要があります。変数を arr4[i] メモリ アドレスに戻します。これにより、内部ループの反復ごとにメモリ負荷が 5 回の読み取り、3 回の書き込みから 1 回の読み取り、1 回の書き込みに減少し、コストのかかる書き込み後の読み取りパイプラインのストールがなくなります。

これらのメモリー書き込みは k ループ内で行われるため、初期ロードと最終ストアを k ループの外に移動すると、i ループの反復ごとに (5+3)*160 = 1280 メモリー I/O からメモリー負荷が削減されます。 i ループの反復ごとに 2 つのメモリ I/O に。ああ、初期値は反復回数であるため、初期化ループ全体 (z ループ) も同様に削除できます。したがって、メモリ I/O を i 回の反復ごとに 1 に減らすことができます。

このようなもの:

for (int j=0; j < 8; ++j)
{
    int * __restrict const arr4 = (int *) _alloca(16000000*sizeof(int));

    for (int i = 0; i < 16000000; ++i)
    {
       int v = i;    // eliminate initialization pass (z loop)
       for (int k = 0; k < 160; ++k)
       {
           v -= (7 - v * 6 % (i+77) + 5 * 4 / 3 + 3 % 2 + 1 - (i+7));
           v += ((77 - 2 - (i+9)/2 + v));
           v *= (8 - 2 + 6 % 3 / 2 + (i+6));
       }
       arr4[i] = v;
    }
    printf(" %i ", arr4[((j+1)*666)%16]);
}

メモリ書き込みは通常、現在のコンテキスト外の未知の当事者によって観察される可能性があるため、神聖であると見なされるため、コンパイラは常にこの最適化を行うとは限りません。状況についてコンパイラよりも多くのことを知っていれば、コンパイラよりも優れたコードを書くことができます。

于 2013-06-06T17:04:14.373 に答える