【问题标题】:When is sizeof(myPOD) too big for pass by value on x64?什么时候 sizeof(myPOD) 太大而无法在 x64 上传递值?
【发布时间】:2012-03-12 18:34:05
【问题描述】:

对于大小不超过 8 字节的结构,我预计没有区别,但更大的 POD 类型呢?当类型的大小超过机器字大小时,按值传递是否会变得更加昂贵,或者是否有其他因素(如缓存行大小)会影响性能?

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

说明:

  • 我可能想得太狭隘了,因为我不了解在其中发挥作用的所有内容(寄存器、调用约定、编译器优化)。我主要对微软的 C++ 编译器感兴趣,它只使用 __fastcall。
  • 我很感兴趣,在了解架构、类型大小、缓存大小等的参数传递方面是否有任何一般性建议。例如:“当类型小于 N 字节时,最好按值传递。 "其中 N 是可以从我们知道的事物中推导出来的事物。

【问题讨论】:

  • 在某个时刻,远远超过 8 个字节,编译器生成代码以创建本地副本并将指针传递给该副本。试一试,看看生成的机器码。

标签: c++ c x86 64-bit x86-64


【解决方案1】:

您混淆了两个不同的问题。您可以按值传递 任何 对象(只要它是可复制的)。

是否将其传递到寄存器中或堆栈上取决于实现,特别是所使用的调用约定。

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

在某些情况下,对象可能从不传入寄存器,不管它们的大小。

同样,SIMD 值 (SSE/AVX) 在某些调用约定中可能会在寄存器中传递,但在其他调用约定中将始终放在堆栈中。标量浮点值也是如此。

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

显然,对象越大,占用的缓存空间就越多,这意味着您将获得更多的缓存未命中。

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

回应您的修改

我很感兴趣,在了解架构、类型大小、缓存大小等的参数传递方面是否有任何一般性建议。例如:“当类型小于 N 字节时,最好按值传递类型。

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

其次,您正在研究一种微优化,无论哪种方式都不太可能产生明显的差异。对于小对象,按值传递避免了指针间接,因此它可能会稍微快一些。在某些时候,这会被复制的成本所淹没(假设对象被复制,见上文)。对于 非常 大的对象(为了论证,假设 500 字节或以上,大到对象 通常 达不到它),您绝对应该通过引用传递.

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

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

  1. 做看起来很自然的事情:如果通过副本使您的代码更简单或更清晰,那就这样做。
  2. 如果性能很重要,那么 (1) 确保您所查看的内容实际上对您的性能有任何明显的影响。测量它。如果它影响性能,那么它可以被测量。如果无法衡量,那么从定义上讲,性能差异就不会很明显。

所以,简而言之:

  • 对于原始类型,按值传递。
  • 对于非常大的类型,通过引用传递。
  • 除此之外,别再担心了,把时间花在富有成效的事情上。

【讨论】:

  • 我知道任何对象都可以按值传递,只要我知道所有细节,我只想要一些硬数字。似乎我不知道所有的事情。请参阅我的说明。
  • 如果您想要硬数字,请测量它。 :) 顺便说一句,如果您关心性能,这也是唯一值得遵循的规则:如果您需要知道哪个更快,请测量它。如果您无法衡量差异,那也没关系。
  • 我想我正在寻找的是 x64 调用约定的文档。 msdn.microsoft.com/en-us/library/ms235286(v=vs.100).aspx 很好地解释了这一点:“任何不适合 8 字节或不是 1、2、4 或 8 字节的参数都必须通过引用传递。没有尝试将单个参数传播到多个寄存器。”
【解决方案2】:

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

数据复制需要时间。结构越大,复制它所需的时间就越长。是否是性能取决于您执行此操作的频率,以及您的代码对性能的要求。

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

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

【讨论】:

  • “x86 默认不这样做”?什么? x86 默认不做任何事情,所以没有什么可以“覆盖”。由编译器决定调用约定。
  • @jalf,是的,我不准确。普通的 x86 调用约定,即 Linux 和 Windows,不这样做。
猜你喜欢
  • 2017-10-14
  • 2017-04-15
  • 1970-01-01
  • 2011-08-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-13
  • 1970-01-01
相关资源
最近更新 更多