2

C プロジェクトの行列処理関数を設計しています。行列を値または参照で渡すことを検討しています。値と参照で行列を渡すベンチマークを作成しましたが、どちらも gcc の最適化フラグ -O0 と -O2 で同じように動作するようです。私のベンチマークが間違った結果を示している可能性があることを考えると、C のみを使用して関数呼び出しの内外で行列を渡す最も効率的な方法を知りたい.

#include <stdio.h>
#include <time.h>

// Compiled on OSX 10.6.8 using: cc -o matrix matrix.c -std=c99 -O2

typedef struct {
    float m0;
    float m1;
    float m2;
    float m3;
    float m4;
    float m5;
    float m6;
    float m7;
    float m8;
    float m9;
    float m10;
    float m11;
    float m12;
    float m13;
    float m14;
    float m15;
} Matrix;

// ================================================
//                 Pass By Value
// ------------------------------------------------

Matrix PassByValue (Matrix a, Matrix b) {
    Matrix matrix;

    matrix.m0  = a.m0 * b.m0  + a.m4 * b.m1  + a.m8  * b.m2  + a.m12 * b.m3;
    matrix.m1  = a.m1 * b.m0  + a.m5 * b.m1  + a.m9  * b.m2  + a.m13 * b.m3;
    matrix.m2  = a.m2 * b.m0  + a.m6 * b.m1  + a.m10 * b.m2  + a.m14 * b.m3;
    matrix.m3  = a.m3 * b.m0  + a.m7 * b.m1  + a.m11 * b.m2  + a.m15 * b.m3;

    matrix.m4  = a.m0 * b.m4  + a.m4 * b.m5  + a.m8  * b.m6  + a.m12 * b.m7;
    matrix.m5  = a.m1 * b.m4  + a.m5 * b.m5  + a.m9  * b.m6  + a.m13 * b.m7;
    matrix.m6  = a.m2 * b.m4  + a.m6 * b.m5  + a.m10 * b.m6  + a.m14 * b.m7;
    matrix.m7  = a.m3 * b.m4  + a.m7 * b.m5  + a.m11 * b.m6  + a.m15 * b.m7;

    matrix.m8  = a.m0 * b.m8  + a.m4 * b.m9  + a.m8  * b.m10 + a.m12 * b.m11;
    matrix.m9  = a.m1 * b.m8  + a.m5 * b.m9  + a.m9  * b.m10 + a.m13 * b.m11;
    matrix.m10 = a.m2 * b.m8  + a.m6 * b.m9  + a.m10 * b.m10 + a.m14 * b.m11;
    matrix.m11 = a.m3 * b.m8  + a.m7 * b.m9  + a.m11 * b.m10 + a.m15 * b.m11;

    matrix.m12 = a.m0 * b.m12 + a.m4 * b.m13 + a.m8  * b.m14 + a.m12 * b.m15;
    matrix.m13 = a.m1 * b.m12 + a.m5 * b.m13 + a.m9  * b.m14 + a.m13 * b.m15;
    matrix.m14 = a.m2 * b.m12 + a.m6 * b.m13 + a.m10 * b.m14 + a.m14 * b.m15;
    matrix.m15 = a.m3 * b.m12 + a.m7 * b.m13 + a.m11 * b.m14 + a.m15 * b.m15;

    return matrix;
}


// ================================================
//               Pass By Reference
// ------------------------------------------------

void PassByReference (Matrix* matrix, Matrix* a, Matrix* b) {
    if (!matrix) return;
    if (!a) return;
    if (!b) return;

    matrix->m0  = a->m0 * b->m0  + a->m4 * b->m1  + a->m8  * b->m2  + a->m12 * b->m3;
    matrix->m1  = a->m1 * b->m0  + a->m5 * b->m1  + a->m9  * b->m2  + a->m13 * b->m3;
    matrix->m2  = a->m2 * b->m0  + a->m6 * b->m1  + a->m10 * b->m2  + a->m14 * b->m3;
    matrix->m3  = a->m3 * b->m0  + a->m7 * b->m1  + a->m11 * b->m2  + a->m15 * b->m3;

    matrix->m4  = a->m0 * b->m4  + a->m4 * b->m5  + a->m8  * b->m6  + a->m12 * b->m7;
    matrix->m5  = a->m1 * b->m4  + a->m5 * b->m5  + a->m9  * b->m6  + a->m13 * b->m7;
    matrix->m6  = a->m2 * b->m4  + a->m6 * b->m5  + a->m10 * b->m6  + a->m14 * b->m7;
    matrix->m7  = a->m3 * b->m4  + a->m7 * b->m5  + a->m11 * b->m6  + a->m15 * b->m7;

    matrix->m8  = a->m0 * b->m8  + a->m4 * b->m9  + a->m8  * b->m10 + a->m12 * b->m11;
    matrix->m9  = a->m1 * b->m8  + a->m5 * b->m9  + a->m9  * b->m10 + a->m13 * b->m11;
    matrix->m10 = a->m2 * b->m8  + a->m6 * b->m9  + a->m10 * b->m10 + a->m14 * b->m11;
    matrix->m11 = a->m3 * b->m8  + a->m7 * b->m9  + a->m11 * b->m10 + a->m15 * b->m11;

    matrix->m12 = a->m0 * b->m12 + a->m4 * b->m13 + a->m8  * b->m14 + a->m12 * b->m15;
    matrix->m13 = a->m1 * b->m12 + a->m5 * b->m13 + a->m9  * b->m14 + a->m13 * b->m15;
    matrix->m14 = a->m2 * b->m12 + a->m6 * b->m13 + a->m10 * b->m14 + a->m14 * b->m15;
    matrix->m15 = a->m3 * b->m12 + a->m7 * b->m13 + a->m11 * b->m14 + a->m15 * b->m15;
}

// ================================================
//                  Benchmark
// ------------------------------------------------

#define LOOPS 100000

int main () {
    Matrix result;
    Matrix a;
    Matrix b;
    clock_t begin;
    clock_t end;
    int index;

    // ------------------------------------------
    //          Pass By Reference
    // ------------------------------------------
    begin = clock();
    for (index = 0; index < LOOPS; index++) {

        PassByReference(&result,&a,&b);
        a.m0 += index;
        b.m0 += index;

    }
    end = clock();
    printf("Pass By Ref: %f\n",(double)(end - begin) / CLOCKS_PER_SEC);

    // ------------------------------------------
    //            Pass By Value
    // ------------------------------------------
    begin = clock();
    for (index = 0; index < LOOPS; index++) {

        result = PassByValue(a,b);
        a.m0 += index;
        b.m0 += index;

    }
    end = clock();
    printf("Pass By Val: %f\n",(double)(end - begin) / CLOCKS_PER_SEC);


    // The following line along with the above
    // additions in the loops hopefully prevent
    // the matrices from being optimized into
    // nothing.
    printf("%0.1f\n",result.m0);

    return 0;
}

結果:

Pass By Ref: 0.489226
Pass By Val: 0.488882
4

4 に答える 4

0

効果的な C++ から:

通常、値渡しよりも参照渡しから const への受け渡しの方が効率的で、スライスの問題を回避できます。この規則は、組み込み型、STL 反復子、および関数オブジェクト型には適用されません。それらの場合、通常は値渡しが適切です。

あなたが C++ ではなく C でプログラミングしていることは理解していますが、この規則は依然として当てはまると思います。これら2つの例が非常に近いパフォーマンスを示す理由は、構造体にfloatのみが含まれており、値で渡されるためコピーするのに費用がかからないためです。

ただし、Effective C++の作者が言ったように

一部のコンパイラは、定期的に裸の double を喜んで配置しているにもかかわらず、double のみで構成されるオブジェクトをレジスタに配置することを拒否します。そのようなことが起こった場合、そのようなオブジェクトを参照渡ししたほうがよい場合があります。これは、コンパイラーが確実にポインターをレジスターに入れるためです。".Unsubscribe-lgm-thur

あなたの場合、おそらくマシンは構造体をレジスタに入れることを気にしませんが、他のマシンでプログラムをいつ実行するかを判断するのは困難です。彼らのパフォーマンスは非常に近いので、私は参照渡しに投票します.

于 2013-03-29T01:16:20.923 に答える
0

技術的には、C には「値渡し」しかありません。マトリックス ポインターを (値渡しで) 関数に渡す必要があります。関数に「コピー」されるデータが減るため、より効率的になります。

于 2013-03-29T03:44:04.533 に答える
0

ここで 2 つの競合する利益があります。

  1. 構造体を値で渡すと、これはデータ ストレージ クラスとして型指定され、x86 呼び出し規則によってスタックにプッシュされます。これは、レジスタでスタックする ref 呼び出しよりも少し遅くなります。

  2. これは、一連のポインター逆参照によってほぼ正確にバランスが取れています...

各パーツを個別に分離してプロファイルする

この種のコードを高速化しようとしている場合は、ある種の SIMD コード、AltiVec、SSE、または OpenCL に応じて、より高速な実装を記述できる可能性があります。

于 2013-03-29T01:45:36.800 に答える
0

いずれにせよ、32 個の float 値はレジスタに収まりません。コンパイラは、データをメモリからスタックにプッシュするように強制されますが、スタックはメモリの別の部分にすぎません。データ アクセスの数によっては、ポインターを逆参照する代わりにデータをコピーする方がさらに遅くなる場合があります。

非スカラー データには const 修飾子を使用した参照渡しを使用することをお勧めします。特定のプラットフォーム向けにコードを最適化するのは、コンパイラの仕事です。

于 2013-03-29T01:49:15.720 に答える