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++:

更喜欢 pass-by-reference-to-const 而不是 pass-by-value,它通常更有效,并且避免了切片问题。该规则不适用于内置类型和 STL 迭代器和函数对象类型。对于他们来说,按值传递通常是合适的。

我了解您使用 C 而不是 C++ 进行编程,但我认为这条规则仍然适用。您的这两个示例执行非常接近的原因可能是该结构仅包含浮点数并且复制成本低廉,因为它是按值传递的。

但是,就像 Effective C++ 的作者所说

一些编译器拒绝将只包含双精度的对象放入寄存器,即使他们很乐意定期将裸双精度放在那里。当这种事情发生时,您最好通过引用传递此类对象,因为编译器肯定会将指针放入寄存器中”。Unsubscribe-lgm-thur

在您的情况下,也许机器不介意将结构放入寄存器中,但是当您在其他机器上运行程序时很难判断。由于他们的表现非常接近,我会投票支持通过引用。

于 2013-03-29T01:16:20.923 回答
0

您在这里有 2 个相互竞争的利益:

  1. 按值传递结构,这将被键入为数据存储类并通过 x86 调用约定推送到堆栈上,这比会卡在寄存器中的 by ref 调用慢一点。

  2. 这几乎完全由一堆指针取消引用来平衡......

分开并分别剖析每个部分

如果您试图使这种代码更快,您可以在某种 SIMD 代码、AltiVec、SSE 或 OpenCL 中编写更快的实现,具体取决于

于 2013-03-29T01:45:36.800 回答
0

无论如何,32 个浮点值都不适合寄存器。编译器将被迫将数据从内存推入堆栈,堆栈只是内存的另一部分。根据数据访问的数量,复制数据而不是取消引用指针可能会更慢。

我建议对任何非标量数据使用带有 const 修饰符的传递引用。编译器的工作是针对特定平台优化您的代码。

于 2013-03-29T01:49:15.720 回答
0

从技术上讲,我们在 C 中只有“按值传递”。您应该将矩阵指针(按值)传递给函数。它将减少“复制”到函数中的数据,从而提高效率。

于 2013-03-29T03:44:04.533 回答