【问题标题】:By reference or value通过参考或价值
【发布时间】:2014-08-18 15:30:10
【问题描述】:

如果我有以下结构的实例

type Node struct {
    id          string 
    name        string  
    address     string
    conn        net.Conn
    enc         json.Encoder
    dec         json.Decoder
    in          chan *Command
    out         chan *Command
    clients     map[string]ClientNodesContainer
}

我不明白什么时候应该通过引用发送一个结构,什么时候应该通过值发送它(考虑到我不想对该实例进行任何更改),是否有一个经验法则可以使它更容易决定?

我所能找到的只是当结构很小或复制成本低廉时按值发送结构,但例如,小真的意味着小于 64 位地址吗?

如果有人能指出一些更明显的规则会很高兴

【问题讨论】:

  • 我想知道的是stackoverflow.com/questions/23542989/…。至于什么是“大”,肯定是超过24字节(x64上一个slice header的大小,我们经常传值);代码审查 cmets 使用直观的经验法则“将所有字段作为参数传递会不会感觉很尴尬?”;我会说,粗略地说,如果你小于 64 字节(x64 上缓存行的大小)并且按值传递感觉还可以,那么不值得浪费性能(除非它是性能关键代码,在这种情况下测量它)。

标签: go


【解决方案1】:

规则很简单:

Go 中没有“按引用传递/发送”的概念,你所能做的就是按值传递。

关于是否传递结构的值或指向结构的指针的问题(这不是通过引用调用!):

  1. 如果要修改函数或方法内部的值:传递指针。
  2. 如果您不想修改该值:
    1. 如果您的结构:使用指针。
    2. 否则:没关系。

所有这些考虑复制成本的想法都是在浪费时间。副本很便宜,即使对于中型结构也是如此。传递指针可能是分析之后的合适优化。

你的结构不大。一个大型结构包含像wholeWorldBuf [1000000]uint64 这样的字段。 像您这样的微小结构可能会或可能不会从传递指针中受益,而任何提供建议哪个更好的人都在撒谎:这完全取决于您的代码和调用模式。

如果您用尽了合理的选项并且分析表明复制您的结构所花费的时间:尝试使用指针。

【讨论】:

  • “大”的定义还取决于您运行的平台。如果您的平台无法在几个周期内复制您的结构,那么它太大并且可能应该通过引用传递(或者您可以忍受性能损失)。
  • “传递更多价值”通常是推动 Go 新人的正确方向(否则你会得到很多 *[]byte 和类似的愚蠢),但我不会让它成为一个近乎绝对的原则像这样;指针对于小于 8M 缓冲区的东西是有意义的。关于指针接收器与值接收器的代码审查 cmets 部分有一个有趣的句子:“假设它相当于将其所有元素作为参数传递给方法。如果感觉太大,那么接收器也太大了。”
【解决方案2】:

我同意“通常为您不打算改变的小结构传递值”的原则,但是现在这个结构在 x64 上是 688 字节,大多数是在嵌入式 json 结构中。细分为:

  • 16*4=64 用于三个字符串(指针/len 对)和 net.Conn(接口值)
  • 208 用于嵌入式json.Encoder
  • 392 用于嵌入式json.Decoder
  • 8*3=24 表示三个 chan/map 值(必须是指针)

Here's 是我用来获取它的代码,不过您需要将其保存在本地才能运行它,因为它使用了unsafe.Sizeof

您可以嵌入 *Encoder/*Decoder 而不是指针,从而保留 104 个字节。不过,我认为保持原样并传递*Nodes 是合理的。

receiver 上的 Go code review comments 类型说“多大是大?假设它相当于将其所有元素作为参数传递给方法。如果感觉太大,那么对于收件人。”这里有意见分歧的余地,但对我来说,九个值,一些多个词,甚至在得到精确数字之前就“感觉很大”。

"Pass Values" section 评论 cmets 文档中说“此建议不适用于大型结构,甚至可能增长的小型结构。”并不是说“大”的标准适用于接收器的普通参数,但我认为这是一种合理的方法。

在代码审查文档中提到,确定大小的部分微妙之处在于,Go 中的许多东西都是内部指针或包含引用的小型结构:切片(但不是固定大小的数组)、字符串、@987654335 @ 值、函数值、通道、映射。您的结构可能拥有一个指向 64KB 缓冲区的[]byte,或者通过interface 拥有一个大结构,但复制起来仍然很便宜。我在另一个答案中wrote some about this,但我在那里所说的并没有阻止人们不得不做出一些不太科学的判断。

看看标准库方法做了什么很有趣。 bytes.Replace 有十个单词的参数,所以至少有时可以复制到堆栈中。另一方面,go/ast 倾向于传递引用(* 指针或接口值),即使对于其非巨大结构上的非变异方法也是如此。结论:很难将其简化为几个简单的规则。 :)

【讨论】:

    【解决方案3】:

    大多数情况下,您应该使用通过引用传递。喜欢:

    func (n *Node) exampleFunc() {
        ...
    }
    

    仅当您希望使用按值传递实例时,您希望确保您的实例不受更改的影响。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-03
      • 1970-01-01
      • 2015-02-06
      • 1970-01-01
      • 2015-07-26
      • 1970-01-01
      • 2014-02-09
      • 2011-07-13
      相关资源
      最近更新 更多