草庐IT

pointers - 我们是否在 Go 中过度使用了传递指针?

coder 2023-06-25 原文

这个问题特定于函数调用,并且针对 Go 优化器在按值传递结构与按指针传递结构时的可信度。

如果您想知道何时在结构字段中使用值与指针,请参阅:Go - Performance - What's the difference between pointer and value in struct?

请注意:我已尽量使任何人都易于理解,因此有些术语并不精确。

一些低效的 Go 代码

假设我们有一个结构:

type Vec3 struct{
  X, Y, X float32
}

我们想要创建一个函数来计算两个向量的叉积。 (对于这个问题,数学并不重要。)有几种方法可以解决这个问题。一个天真的实现是:

func CrossOf(a, b Vec3) Vec3{
  return Vec3{
    a.Y*b.Z - a.Z*b.Y,
    a.Z*b.X - a.X*b.Z,
    a.X*b.Y - a.Y*b.X,
  }
}

将通过以下方式调用:

a:=Vec3{1,2,3}
b:=Vec3{2,3,4}
var c Vec3

// ...and later on:
c := CrossOf(a, b)

这工作正常,但在 Go 中,它显然不是很有效。 ab 按值传递(复制)到函数中,结果再次复制出来。虽然这是一个小例子,但如果我们考虑大型结构,问题就会更加明显。

更有效的实现方式是:

func (res *Vec3) CrossOf(a, b *Vec3) {
  // Cannot assign directly since we are using pointers.  It's possible that a or b == res
  x := a.Y*b.Z - a.Z*b.Y
  y := a.Z*b.X - a.X*b.Z
  res.Z = a.X*b.Y - a.Y*b.X 
  res.Y = y
  res.X = x
}

// usage
c.CrossOf(&a, &b)

这更难阅读,占用更多空间,但效率更高。如果传递的结构非常大,这将是一个合理的权衡。

对于大多数具有类 C 编程背景的人来说,尽可能通过引用传递是直观的,纯粹是为了提高效率。

在 Go 中,直觉认为这是最好的方法,但 Go 本身指出了这种推理的缺陷。

Go 比这更聪明

这里有一些在 Go 中可以工作,但不能在大多数低级类 C 语言中工作的东西:

func GetXAsPointer(vec Vec3) *float32{
  return &vec.X
}

我们分配了一个Vec3,获取了一个指向X字段的指针,并将它返回给调用者。看到问题了吗?在 C 中,当函数返回时,堆栈将展开,返回的指针将变为无效。

但是,Go 是垃圾回收的。它将检测到此 float32 必须继续存在,并将其(float32 或整个 Vec3)分配到堆上而不是堆栈。

Go 需要逃逸检测才能正常工作。它模糊了按值传递和按指针传递之间的界限。

众所周知,Go 是为积极优化而设计的。如果通过引用传递更有效,并且传递的结构不被函数改变,为什么 Go 不应该采用更有效的方法?

因此,我们的高效示例可以重写为:

func (res *Vec3) CrossOf(a, b Vec3) {
    res.X = a.Y*b.Z - a.Z*b.Y
    rex.Y = a.Z*b.X - a.X*b.Z
    res.Z = a.X*b.Y - a.Y*b.X
}

// usage
c.CrossOf(a, b)

请注意,这更具可读性,如果我们假设一个激进的按值传递到按指针传递编译器,就像以前一样高效。

根据文档,建议使用指针传递足够大的接收器,并以与参数相同的方式考虑接收器:https://golang.org/doc/faq#methods_on_values_or_pointers

Go 已经对每个变量进行了逃逸检测,以确定它是放在堆上还是堆栈上。因此,如果结构将被函数更改,则在 Go 范例中似乎更倾向于仅通过指针传递。这将产生更易读且更不容易出错的代码。

Go 编译器是否自动将传值优化为传指针?看起来应该如此。

问题来了

对于结构,我们什么时候应该使用指针传递还是值传递?

需要注意的事项:

  • 对于结构,一个实际上比另一个更有效,还是 Go 编译器会将它们优化为相同?
  • 依靠编译器来优化它是一种不好的做法吗?
  • 在任何地方传递指针是否更糟糕,从而创建容易出错的代码?

最佳答案

简短回答:是的,您在这里过度使用了传递指针。

这里是一些简单的数学运算...您的结构由三个 float32 对象组成,总共 96 位。假设你在一台 64 位机器上,你的指针是 64 位长,所以在最好的情况下你为自己节省了微不足道的 32 位复制。

作为保存这 32 位的代价,您将强制执行额外的查找(它需要跟随指针然后读取原始值)。它必须在堆上而不是堆栈上分配这些对象,这意味着一大堆额外的开销、垃圾收集器的额外工作以及减少的内存局部性。

在编写高性能代码时,您必须意识到糟糕的内存局部缓存未命中的潜在成本可能非常昂贵。主存延迟可以是L1的100倍。

此外,因为您使用的是指向结构的指针,所以您阻止了编译器进行许多原本可以进行的优化。例如,Go 可能会实现 register calling conventions将来,这将在这里被阻止。

简而言之,节省 4 个字节的复制可能会让您付出相当多的代价,所以是的,在这种情况下,您过度使用了传递指针。我不会仅仅为了提高效率而使用指针,除非结构是这个大小的 10 倍,即使那样,考虑到意外修改可能导致错误,也并不总是很清楚这是否是正确的方法。

关于pointers - 我们是否在 Go 中过度使用了传递指针?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41030545/

有关pointers - 我们是否在 Go 中过度使用了传递指针?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐