当sizeof(myPOD)对于x64上的传递值太大时

When is sizeof(myPOD) too big for pass by value on x64?

本文关键字:sizeof myPOD x64 对于      更新时间:2023-10-16

当涉及到大小高达8字节的结构时,我希望没有什么不同,但更大的POD类型呢?当类型的大小超过机器字大小时,传递值是否会变得更昂贵,或者是否有其他因素(如缓存行大小)会影响性能?

我主要对x64感兴趣,但也可以随意加入一些x86的数字。

澄清:

  • 我可能想得太狭隘了,因为我不知道在这方面起作用的一切(寄存器、调用约定、编译器优化)。我主要对微软的C++编译器感兴趣,它只使用__fastcall
  • 我很感兴趣的是,在了解架构、类型大小、缓存大小等的参数传递方面,是否有任何通用的建议。比如:"当类型小于N字节时,更喜欢按值传递类型。"其中N是可以从我们所知道的东西中派生出来的东西

您混淆了两个独立的问题。您可以通过值传递任何对象(只要它是可复制的)。

它是否会在寄存器中或堆栈中传递取决于实现,特别是所使用的调用约定。

在某些调用约定下,大于8字节(通用寄存器大小)的参数将在堆栈上传递。在其他调用约定下,它们可以简单地划分为几个寄存器。

在某些情况下,对象可能是从不在寄存器中传递的,无论其大小如何。

类似地,SIMD值(SSE/AVX)在某些调用约定中可以在寄存器中传递,但在其他调用约定中总是放在堆栈中。标量浮点值也是如此。

但你所问的并不能得到真正有意义的回答。复制对象的速度受对象大小的影响,是的。如果对象是POD类型,并且适合寄存器,则可以使用简单的mov指令来复制。编译器是否执行取决于编译器。

很明显,对象越大,占用的缓存空间就越多,这意味着会有更多的缓存未命中。

但这一切都太模糊了,几乎毫无用处。我们不知道您的对象是什么样子的,也不知道代码对它做了什么。如果您有一个特定的类型,那么编写一个基准测试,看看编译器如何处理它。

响应您的编辑

我很感兴趣的是,当涉及到知道架构、类型大小、缓存大小等的参数传递时,是否有任何通用的建议。比如:"当类型小于N字节时,更喜欢按值传递。

首先,相信你的编译器。在许多情况下,它会积极优化拷贝,因此即使按值传递大对象,也不太可能是一个可衡量的问题。

其次,你看到的是一个微观优化,无论哪种方式都不太可能产生显著的差异。对于小对象,通过值传递可以避免指针间接寻址,因此可能会稍微快一点。在某种程度上,这会被复制成本所淹没(假设对象被复制,请参见上文)。对于非常大的对象(为了便于论证,假设500字节或以上,太大以至于通常无法访问它),您绝对应该通过引用传递。

但是对于8、16、24、40字节的对象呢?谁知道呢?谁在乎?它不太可能在实际代码中产生可衡量的差异。

这让我想到了两条经验法则:

  1. 做一些看起来很自然的事情:如果传递副本使代码更简单或更干净,那么就这样做
  2. 如果性能很重要,那么(1)确保您所看到的内容实际上对您的性能有任何明显的影响。衡量它。如果它影响性能,那么它是可以衡量的。如果不能衡量,那么根据定义,性能的差异就不会明显

简而言之:

  • 对于基元类型,按值传递
  • 对于非常大的类型,通过引用传递
  • 对于其他事情,不要再担心,把时间花在有成效的事情上

您应该关注两件事-数据复制和堆栈使用

数据复制需要时间。结构越大,复制它所需的时间就越长。它是否是一种性能取决于你多久做一次,以及你的代码的性能要求是什么。

堆栈很大,但不是无限的。按值传递大型结构,尤其是与递归结合使用时,很容易导致其溢出。

对于x86_64(使用WIN64或Linux约定),在寄存器中传递数据是一个较小的点。如果每个参数最多8个字节,则前6个在寄存器中传递,这样会更快。对于x86,大多数约定都无法做到这一点(但是Linux内核使用3个寄存器作为参数)
使用药店稍微快一些。但是,与复制8个字节和1000个字节的差异相比,使用堆栈或寄存器传递8个字节之间的差异很小。