C++ 性能与可读性:函数中的本地副本

C++ 性能与可读性:函数中的本地副本,c++,C++,考虑以下代码: Vector2f Box::getCenter() const { const float x = width / 2; const float y = height / 2; return Vector2f(x, y); } 改为这样写是否会提高性能: Vector2f Box::getCenter() const { return Vector2f(width / 2, height / 2); } 我更喜欢第一个,因为它既好看又可读,但

考虑以下代码:

Vector2f Box::getCenter() const
{
    const float x = width / 2;
    const float y = height / 2;

    return Vector2f(x, y);
}
改为这样写是否会提高性能:

Vector2f Box::getCenter() const
{
    return Vector2f(width / 2, height / 2);
}
我更喜欢第一个,因为它既好看又可读,但我开始怀疑,如果我做得太多,是否会损失一些性能,因为它会创建一个额外的不必要的副本


我知道你们中的一些人认为第二个函数同样可读,但这只是一个例子,我更一般地问,在这种情况下,什么是好的编码实践。

我假设
Vector2f
构造函数采用两个
浮点
s。然后,至少使用优化编译器,您将不会看到任何差异

如果您想确切了解您的情况,您可以检查编译器输出(可能告诉您的编译器保留中间文件,在gcc
-S-save temps
)上,或者您可以在线检查。

除非有奇怪的(可能是无意的)类型转换,否则优化编译器将生成相同的代码

两者之间的主要区别在于,在第一种方式中,您可能会强制转换为
float
类型

因此,我更喜欢第二种方法:假设函数的类型更改为
double
,而不是假定的
float
?也就是说

auto x = width / 2;
会更容易接受


如今,许多调试器都支持逐个参数的计算,因此,用第一种方法编写代码并不能帮助调试。

经验法则是:编写可读的代码

C++作为一种语言,其设计的主要目标之一是提高效率。在理想世界中,可读代码是高效代码,C++更接近于此。为了提高性能,您很少被迫编写不可读的代码。即使这样,你也不应该从一开始就这么做。始终首先编写可读性代码。只有当你测量时,你才能知道什么是更多和什么是更少的表现

这是一个很好的检查编译器输出的工具:我希望任何一个好的编译器在启用优化时都会发出非常类似的程序集

这是另一个可以让您轻松对代码进行基准测试的工具:。在你知道事情会有所不同之前,不要开始优化它

编译器在优化事物方面相当强大,而且大多数时候他们比你更了解。当然,您不应该做“愚蠢”的事情,例如,显然您不会
返回向量2f(宽度*sin(pi/2)*exp(ln(0.5)),高度/2)即使一个超级聪明的编译器可以意识到它与您的原始代码相同

最后但并非最不重要的一点是,不要忘记,您主要是为人类阅读和理解编写代码的

PS:可读性非常主观。有人可能会说,第二个更具可读性,我甚至会更进一步地写

Vector2f Box::getCenter() const
{
    return {width * 0.5 , height * 0.5};
}

这还有一个额外的好处,即如果您决定更改返回类型(或者如果您将值类型更改为
int
),则不必更改正文

下面是一段用C++编写的代码:

Vector2f GetCenterLong(float width, float height)
{
    const float x = width / 2;
    const float y = height / 2;

    return Vector2f(x, y);
}

Vector2f GetCenterShort(float width, float height)
{
    return Vector2f(width / 2, height / 2);
}
下面是使用x64 msvc v19.21和-Ox优化生成的汇编代码。 您可以注意到这两个函数之间没有区别

__$ReturnUdt$ = 8
width$ = 16
height$ = 24
Vector2f GetCenterLong(float,float) PROC                ; GetCenterLong
        mulss   xmm1, DWORD PTR __real@3f000000
        mov     rax, rcx
        mulss   xmm2, DWORD PTR __real@3f000000
        movss   DWORD PTR [rcx], xmm1
        movss   DWORD PTR [rcx+4], xmm2
        ret     0
Vector2f GetCenterLong(float,float) ENDP                ; GetCenterLong

__$ReturnUdt$ = 8
width$ = 16
height$ = 24
Vector2f GetCenterShort(float,float) PROC             ; GetCenterShort
        mulss   xmm1, DWORD PTR __real@3f000000
        mov     rax, rcx
        mulss   xmm2, DWORD PTR __real@3f000000
        movss   DWORD PTR [rcx], xmm1
        movss   DWORD PTR [rcx+4], xmm2
        ret     0
Vector2f GetCenterShort(float,float) ENDP             ; GetCenterShort
因此,当编译器优化是可能的时,更倾向于可读性而不是简洁性

改为这样写是否会提高性能:

Vector2f Box::getCenter() const
{
    return Vector2f(width / 2, height / 2);
}
那么,你衡量了业绩增长了吗?当你关心绩效时,一定要衡量

性能可能会有所不同。如果
width
height
的类型以及参数不是
float
,则局部变量引入两种转换,必须执行这两种转换。执行转换可能比不执行转换慢,并且也可能导致不同的结果。通过使用
auto
类型推断,可以纠正对类型的依赖性

但是如果我们假设不涉及转换,那么就没有理由假设一个优化者不能在两种情况下生成完全相同的程序,在这种情况下,性能将完全相同。您可以比较编译器生成的程序集进行验证

我问的是更一般的问题,在这种情况下,什么是好的编码实践

默认情况下,可读性更为重要。通常,在这种情况下,可读性也是主观的


性能可能更重要,如果您测量瓶颈,但这种变化不大可能实现性能。

几乎可以肯定的是,现代C++编译器在两种情况下都会生成相同的代码。您可以自己回答自己的问题:所有编译器都提供了生成机器语言代码而不是二进制可执行文件的选项。在这两种情况下都使用这个选项,看看是否有任何区别。还要注意,“可读性”是非常主观的。对于一个有经验的程序员来说,这两个变量的可读性可能是相同的(反正对我来说也是如此)。它们看起来很重要,但如果不仔细检查周围的代码,就很难猜测它们是否重要或如何重要。事实上,第二个代码对我来说更具可读性。我可以立即看出,我们正在将宽度和高度减半。对于第一个例子,我必须将x和y存储到我的大脑寄存器中,然后加载它们来解释返回,这会产生开销…@L.F.对于给定的例子,是的,但对于更复杂的例子,这是一个很大的帮助。尤其是有好名声的
halfWidth
halfHeight
在这里可能是更好的名称,除非有一个与意图相关的更具体的名称。回答得很好。很高兴知道我的编译器将优化像这样的东西。我会毫不羞耻地继续用第一种方式写它:我不会投反对票,但我不同意这个观点