按引用或按值传递

huangapple go评论85阅读模式
英文:

By reference or value

问题

如果我有以下结构体的实例:

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位地址小?

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

英文:

if i had an instance of the following struct

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
}

i am failing to understand when i should send a struct by reference and when should i send it by value(considering that i do not want to make any changes to that instance), is there a rule of thumb that makes it easier to decide?

all i could find is send a struct by value when its small or inexpensive to copy, but does small really mean smaller than 64bit address for example?

would be glad if someone can point some more obvious rules

答案1

得分: 11

规则非常简单:

> 在Go语言中,没有“按引用传递”的概念,你只能通过值传递。

关于是传递结构体的值还是结构体的指针(这不是按引用传递!)的问题:

  1. 如果你想在函数或方法内修改值:传递指针。
  2. 如果你不想修改值:
    1. 如果你的结构体很大:使用指针。
    2. 否则:没有什么区别。

所有关于复制的成本如何的思考都是浪费时间的。复制是廉价的,即使对于中等大小的结构体也是如此。传递指针可能是一种适当的优化,进行性能分析之后。

你的结构体不是很大。一个大的结构体包含像wholeWorldBuf [1000000]uint64这样的字段。
像你的这种小结构体可能会或可能不会从传递指针中受益,任何给出哪种方式更好的建议的人都是在说谎:这完全取决于你的代码和调用模式。

如果你没有明智的选择,并且性能分析显示时间花在复制结构体上:尝试使用指针进行实验。

英文:

The rule is very simple:

> There is no concept of "pass/send by reference" in Go, all you can do is pass by value.

Regarding the question whether to pass the value of your struct or a pointer to your struct (this is not call by reference!):

  1. If you want to modify the value inside the function or method: Pass a pointer.
  2. If you do not want to modify the value:
    1. If your struct is large: Use pointer.
    2. Otherwise: It just doesn't matter.

All this thinking about how much a copy costs you is wasted time. Copies are cheap, even for medium sized structs. Passing a pointer might be a suitable optimization after profiling.

Your struct is not large. A large struct contains fields like wholeWorldBuf [1000000]uint64.
Tiny structs like yours might or might not benefit from passing a pointer and anybody who gives advice which one is better is lying: It all depends on your code and call patterns.

If you run out of sensible options and profiling shows that time is spent copying your structs: Experiment with pointers.

答案2

得分: 2

“通常为不打算更改的小结构体传递值”的原则我同意,但是这个结构体目前在x64上有688字节,其中大部分是嵌入的json结构体。具体情况如下:

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

这里是我用来获取这些信息的代码,但你需要将其保存到本地才能运行,因为它使用了unsafe.Sizeof

你可以嵌入*Encoder/*Decoder而不是指针,这样可以减少到104字节。不过,我认为保持原样并传递*Node是合理的。

关于接收器类型的Go 代码审查评论中说:“大的有多大?假设它等同于将其所有元素作为参数传递给方法。如果感觉太大,那么对于接收器来说也太大了。”在这里可能存在不同意见,但对我来说,九个值,一些多个单词,“感觉很大”,即使在得到精确数字之前。

“传递值”部分的审查评论文档中说:“这个建议不适用于大的结构体,甚至不适用于可能会增长的小结构体。”它没有说对于普通参数与接收器来说,“大”的标准是相同的,但我认为这是一个合理的方法。

在代码审查文档中提到的确定“大”的一部分微妙之处是,Go中的许多东西在内部是指针或小的、包含引用的结构体:切片(但不包括固定大小的数组)、字符串、interface值、函数值、通道、映射。你的结构体可能拥有一个指向64KB缓冲区的[]byte,或者通过interface拥有一个大的结构体,但复制起来仍然很廉价。我在另一个答案中写了一些相关内容,但我在那里说的并不能阻止我们不得不做出一些不太科学的判断。

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

英文:

The principle of "usually pass values for small structs you don't intend to mutate" I agree with, but this struct, right now, is 688 bytes on x64, most of those in the embedded json structs. The breakdown is:

  • 16*4=64 for the three strings (pointer/len pairs) and the net.Conn (an interface value)
  • 208 for the embedded json.Encoder
  • 392 for the embedded json.Decoder
  • 8*3=24 for the three chan/map values (must be pointers)

Here's the code I used to get that, though you need to save it locally to run it because it uses unsafe.Sizeof.

You could embed *Encoder/*Decoder instead of pointers, leaving you at 104 bytes. I think it's reasonable to keep as-is and pass *Nodes around, though.

The Go code review comments on receiver type say "How large is large? Assume it's equivalent to passing all its elements as arguments to the method. If that feels too large, it's also too large for the receiver." There is room for differences of opinion here, but for me, nine values, some multiple words, "feels large" even before getting precise numbers.

In the "Pass Values" section the review comments doc says "This advice does not apply to large structs, or even small structs that might grow." It doesn't say that the same standard for "large" applies to normal parameters as to receivers, but I think that's a reasonable approach.

Part of the subtlety of determining largeness, alluded to in the code review doc, is that many things in Go are internally pointers or small, reference-containing structs: slices (but not fixed-size arrays), strings, interface values, function values, channels, maps. Your struct may own a []byte pointing to a 64KB buffer, or a big struct via an interface, but still be cheap to copy. I wrote some about this in another answer, but nothing I said there prevents one from having to make some less-than-scientific judgement calls.

It's interesting to see what standard library methods do. bytes.Replace has ten words' worth of args, so that's at least sometimes OK to copy onto the stack. On the other hand go/ast tends to pass around references (either * pointers or interface values) even for non-mutating methods on its not-huge structs. Conclusion: it can be hard to reduce it to a couple simple rules. 按引用或按值传递

答案3

得分: -1

大多数情况下,你应该使用引用传递。例如:

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

只有在希望确保实例不受更改时,才需要使用按值传递实例的情况。

英文:

Most of the time you should use passing by reference. Like:

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

Only situation when you would like to use passing instance by value is when you would like to be sure that your instance is safe from changes.

huangapple
  • 本文由 发表于 2014年8月18日 05:32:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/25353824.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定