C语言 我应该使用矩阵4x4乘法函数的哪个变体?

b5buobof  于 2023-06-21  发布在  其他
关注(0)|答案(1)|浏览(106)

我正在学习使用OpenGL和C进行3D渲染,并编写了一个小型数学库用于学习。使用return语句返回矩阵乘法函数的结果更好,还是通过指针修改输出矩阵更好?

typedef float vec_t;

typedef struct mat4_s {
    vec_t m[4][4];
} mat4_t;

void Mat4Mult(mat4_t* out, const mat4_t* in1, const mat4_t* in2) {
    out->m[0][0] = /* ... */;
    out->m[1][0] = /* ... */;
    /* ... */
}

mat4_t Mat4Mult(const mat4_t* in1, const mat4_t* in2) {
    mat4_t result;
    result.m[0][0] = /* ... */;
    result.m[1][0] = /* ... */;
    /* ... */
    return result;
}

我想知道哪种选择更正确。我认为这两个选项都是正确的,但我更喜欢使用return语句返回函数的结果。如果我说错了请纠正我,我还没有完全掌握C。

yc0p9oo0

yc0p9oo01#

凭直觉回答这些问题是非常困难的,即使你有堆积如山的经验。这就是为什么你应该尝试这两种方法,并分析结果。让我们比较以下简单的4x4矩阵乘法函数:

void Mat4Mult_Dest(mat4_t* out, const mat4_t* in1, const mat4_t* in2) {
    for (int i = 0; i < 4; ++i) {
        for (int j = 0; j < 4; ++j) {
            out->m[i][j] = 0;
            for (int k = 0; k < 4; ++k) {
                out->m[i][j] += in1->m[i][k] * in2->m[k][j];
            }
        }
    }
}

mat4_t Mat4Mult_Ret(const mat4_t* in1, const mat4_t* in2) {
    mat4_t out = {0};
    for (int i = 0; i < 4; ++i) {
        for (int j = 0; j < 4; ++j) {
            out.m[i][j] = 0;
            for (int k = 0; k < 4; ++k) {
                out.m[i][j] += in1->m[i][k] * in2->m[k][j];
            }
        }
    }
    return out;
}
Clang 15.0 Results

GCC 12.2 Results

结果在GCC和clang之间显著不同。查看程序集,这可能是因为clang内联了_Ret版本,但没有对_Dest版本做同样的操作。GCC内联了这两个函数,使它们的性能基本相同。这并不奇怪,因为这两个函数执行相同的计算。

总结

根据基准测试,按值返回至少与写入目标矩阵一样快。它对某些编译器更内联友好,这可能会提高性能。但是,通过对函数进行注解,使其更有可能被内联,也可以获得相同的结果。
值得注意的是,在Mat4Mul_Ret中,return out;无论如何都会就地写入目标,因为大型对象是通过x86_64 ABI中的目标指针传递的:

Mat4Mult_Ret:
// ...
// last 4 instructions move result to destination pointer
movups  xmmword ptr [rdi + 48], xmm1
movups  xmmword ptr [rdi + 32], xmm7
movups  xmmword ptr [rdi + 16], xmm4
movups  xmmword ptr [rdi], xmm3
ret

但是,您的函数之间有一个显着的差异:mat4_t* out可以别名为in1in2,但本地mat4_t out不能。* * 考虑将指针标记为restrict,让编译器有更大的优化自由度。**

相关问题