是否有可能有一个编译时"参考"变量?

时间:2016-08-08 00:32:41

标签: c++ optimization reference

请考虑以下代码:

Matrix4x4 perspective(const ViewFrustum &frustum) {
    float l = frustum.l;
    float r = frustum.r;
    float b = frustum.b;
    float t = frustum.t;
    float n = frustum.n;
    float f = frustum.f;

    return {
        { 2 * n / (r - l), 0,               (r + l) / (r - l),    0                      },
        { 0,               2 * n / (t - b), (t + b) / (t - b),    0                      },
        { 0,               0,               -((f + n) / (f - n)), -(2 * n * f / (f - n)) },
        { 0,               0,               -1,                   0                      }
    };
}

为了提高构造矩阵的可读性,我必须从frustum结构中复制值,或者引用它们。但是,我实际上也不需要复制或间接。

是否有可能有某种"参考"这将在编译时解决,有点像符号链接。它会产生与以下相同的效果:

Matrix4x4 perspective(const ViewFrustum &frustum) {
    #define l frustum.l;
    #define r frustum.r;
    #define b frustum.b;
    #define t frustum.t;
    #define n frustum.n;
    #define f frustum.f;

    return {
        { 2 * n / (r - l), 0,               (r + l) / (r - l),    0                      },
        { 0,               2 * n / (t - b), (t + b) / (t - b),    0                      },
        { 0,               0,               -((f + n) / (f - n)), -(2 * n * f / (f - n)) },
        { 0,               0,               -1,                   0                      }
    };

    #undef l
    #undef r
    #undef b
    #undef t
    #undef n
    #undef f
}

没有预处理器(或者可接受吗?)。我认为它并不是真正需要的,或者在这种特殊情况下可以通过直接将这6个值参数化为函数来避免(尽管如此调用函数会有点恼人 - 但即便如此,我也是如此可以建立内联代理功能。)

但我只是想知道这是否在某种程度上是可能的?我找不到类似的东西。我认为在本地缩短将被大量使用的描述性名称会变得非常方便,而不必丢失原始名称。

3 个答案:

答案 0 :(得分:11)

那就是C ++引用的用途:

const float &l = frustum.l;
const float &r = frustum.r;
const float &b = frustum.b;
const float &t = frustum.t;
const float &n = frustum.n;
const float &f = frustum.f;

大多数现代编译器都会优化引用,并通过在编译时解析引用,在下面的表达式中逐字使用frustum对象中的值。

答案 1 :(得分:11)

强制性免责声明 :不要过早优化。

让我比较你的天真perspective函数,包含

float l = frustum.l;
float r = frustum.r;
float b = frustum.b;
float t = frustum.t;
float n = frustum.n;
float f = frustum.f;

使用define和@Sam Varshavchik解决方案及参考资料。

我们假设我们的编译器正在优化,并且至少优化得体。

所有三个版本的汇编输出:https://godbolt.org/g/G06Bx8

您可以注意到引用和定义版本完全相同 - 正如预期的那样。但天真有很大不同。它首先加载内存中的所有值:

    movss   (%rdi), %xmm2           # xmm2 = mem[0],zero,zero,zero
    movss   4(%rdi), %xmm1          # xmm1 = mem[0],zero,zero,zero
    movss   8(%rdi), %xmm0          # xmm0 = mem[0],zero,zero,zero
    movss   %xmm0, 12(%rsp)         # 4-byte Spill
    movss   12(%rdi), %xmm0         # xmm0 = mem[0],zero,zero,zero
    movss   %xmm0, 8(%rsp)          # 4-byte Spill
    movss   16(%rdi), %xmm3         # xmm3 = mem[0],zero,zero,zero
    movaps  %xmm3, 16(%rsp)         # 16-byte Spill
    movss   20(%rdi), %xmm0

然后再也不会引用%rdifrustrum)内存。另一方面,引用和定义版本在需要时加载值。

这是因为Vector4构造函数的实现对优化器是隐藏的,并且它不能假设构造函数不修改frustrum,所以它必须插入负载,即使这些负载是多余的。

所以,天真的版本甚至可以更快而不是"优化"一,在某些情况下。

答案 2 :(得分:4)

通常,只要您在本地范围内,就可以使用普通引用。现代编译器“透视它们”并将它们视为别名(注意这实际上甚至适用于指针)。

然而,当处理小方面的东西时,复制到局部变量(如果有的话)通常是有益的。 frustnum.r是间接的一层(frustnum实际上是引擎盖下的指针),因此访问它比看起来更昂贵,并且如果在函数中间有函数调用,编译器可能无法证明其价值没有变化,因此可能需要重复访问。

局部变量通常直接在堆栈上(便宜)或直接在寄存器中(最便宜),最重要的是,鉴于它们通常不与“外部”交互,编译器可以更容易地推理它们,因此优化可能更具侵略性;此外,当实际执行计算时,这些值无论如何都将被复制到寄存器和堆栈中。

所以继续使用副本,在最坏的情况下,编译器可能也会这样做,最多可以帮助它优化内容。