V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
gitlight
V2EX  ›  程序员

新手最近在学习 Go,学习的时候有个小问题这几天我一直觉得很困扰。。求助一下大佬们

  •  
  •   gitlight · 2023-01-10 00:44:15 +08:00 · 3613 次点击
    这是一个创建于 690 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Q:当编写实体的 CRUD 接口的时候,到底是向上层传指针呢,还是传实体? 举个例子:

    // 实体
    type person struct {
    	ID   uint
    	name string
    	age  uint
    }
    
    // 指针传递
    func getPerson(ID uint) (*person, error) {
    	return nil,errors.New("err")
    }
    
    // 值传递
    func getPerson_second(ID uint) (person, error) {
    	return person{}, errors.New("err")
    }
    
    

    纠结这个问题其实是因为在写接口的过程中发现,Go 里是没办法让一个实体类直接返回 nil ,只能返回默认构造参数的结构体,不然 ide 会报错,而使用结构体的指针可以返回 nil 。我虽然明白可以通过 err 返回值来处理错误,但是总觉得生成一个和非空的类外表一样的类返回给上层,会有潜在的危险,并且当类比较复杂的时候,可能会产生额外的开销。

    接下来这个延申问题同样很困扰我:像 Go 这样存在指针而且语法很像 C/C++系但同时又存在 GC 的语言,大量使用指针传递对 GC 来说是好还是坏?要是在 IO 密集的 Web 应用场景下,这样大量地使用指针在堆上分配空间会不会加重 GC 的负担,反而会比值传递这种栈复制来得更慢,而不是让程序变得更快?

    第 1 条附言  ·  2023-01-10 11:07:26 +08:00
    谢谢各位的建议,我觉得还是用指针好一些。昨天晚上写出这个问题的时候,我心里已经差不多有答案了,早上去查了一下这个问题的英文版,发现 Stackoverflow 很早就有人问这个问题了,benchmark 的结论是,当结构体不大的时候,传指针和传值开销区别可以忽略。
    在同学推荐给我的时候说 Go 的性能很高,所以下意识地往零成本抽象的去想了,这样看来反而加重了一些思考包袱。。要是搁以前写 spring 早就一把梭写完了
    23 条回复    2023-01-10 13:40:34 +08:00
    wheeler
        1
    wheeler  
       2023-01-10 00:57:52 +08:00 via iPhone
    go 里面惯例是有 error 的话,就不应该假定返回值是可用的吧。

    大量用指针确实会有你说的问题,但是这种情况还是需要 profile 的。

    个人以为,通常从语意以及上下文选择更为合适。
    CEBBCAT
        2
    CEBBCAT  
       2023-01-10 02:18:40 +08:00 via iPhone
    (O) 并且当类比较复杂的时候,可能会产生额外的开销。

    嘻嘻,Go 的好处就是便于 benchmark 和调试。你可以看看性能上到底有没有差异,又是差了几纳秒。

    关于在有 error 情况下该返回 nil 还是零值的问题,我推荐你看看 Go 自己是怎么处理的,也可以参考一些开源代码。

    我个人是返回零值,因为编译器优化、降低 GC 压力。另外你担心零值被使用是吧?那你再多想一步,如果返回指针,那每次用之前是不是还要判断是不是 nil ?何况有了 error ,解决 error 是当务之急。


    你后面的问题好专业,真厉害,我建议你先 Google 一下。应该会有很多 Golang 性能优化的建议,比如 map value 用 plain 值,hot path 之类的。最好中英一起搜。
    jorneyr
        3
    jorneyr  
       2023-01-10 09:04:31 +08:00
    向上层传指针呢,还是传实体?

    个人觉得相差不大,本来 Go 的运行速度也不快,加上 Go 的内存逃逸分析会自动处理一部分,所以无脑的传指针好了。
    bruce0
        4
    bruce0  
       2023-01-10 09:06:59 +08:00
    1. 一般 error 不是 nii 了 就可以认为这个值是有问题了

    2. 返回指针会导致结构体逃逸, 然后分配到堆上, 如果量大的话 会有 gc 压力

    我一般情况下,如果结构体都是基本类型, 而且字段不多的情况下, 不返回指针, 直接用值拷贝, 没有 gc, 如果结构体的字段多, 会用指针了
    seers
        5
    seers  
       2023-01-10 09:18:35 +08:00 via Android
    取决于 struct 的大小,太大传值会有拷贝开销,太小的传指针会有堆开销
    securityCoding
        6
    securityCoding  
       2023-01-10 09:51:13 +08:00
    鹅厂内部框架返回的是指针类似,比较灵活
    hwdef
        7
    hwdef  
       2023-01-10 09:59:29 +08:00
    大量传指针确实有你说的问题,,可能有潜在危险,gc 负担大。。但传值会增大内存使用量,看怎么取舍了
    Rooger
        8
    Rooger  
       2023-01-10 10:01:47 +08:00
    为了统一性,全部使用指针。其实也是符合了 KISS 原则。
    对于你考虑到的 GC 问题,其实是大部分从 C/C++ 转过来的共同的困扰。但是我记得编译器在这块有一定的优化,建议先全部使用指针吧,确定发现有性能问题的时候,再考虑优化吧。前期用最统一,最简单的代码把功能实现出来。
    x1aoYao
        9
    x1aoYao  
       2023-01-10 10:07:17 +08:00
    既没有 Option<T> Result<T, E> 又没有 Exception 就是这样...
    我司一般用的返回指针,比如从数据库里查询实体,GC 消耗有就有吧
    对于判断了 error 是否还要判断 nil 的问题,有些默认的非空,有些用是否空表示数据是否存在(把无数据 error 返回 nil, nil 了, 避免 if err != nil { return err} 直接返回了,不然还要判断 err 的类型或值..
    xylophone21
        10
    xylophone21  
       2023-01-10 10:19:01 +08:00
    外面传一个指针进来,在函数里面赋值. 内存逃逸问题,空对象问题,都解决了. C/C++里都是这么干的, 用起来会烦一些, 性能提升可能也不多, 看你的场景到底在不在乎, 以及这个代码你打算用多久.
    ShayneWang
        11
    ShayneWang  
       2023-01-10 10:19:24 +08:00
    遇事不决用指针
    maigebaoer
        12
    maigebaoer  
       2023-01-10 10:25:55 +08:00 via Android
    @ShayneWang 确实如此,Go 很狗😃
    fioncat
        13
    fioncat  
       2023-01-10 10:29:11 +08:00
    可以期待一下 go1.20 的 arena ,你可以自己决定对象什么时候释放。

    https://github.com/golang/go/issues/51317
    dobelee
        14
    dobelee  
       2023-01-10 10:34:31 +08:00
    日常业务开发直接指针。统一规范。如果大的用指针小的用结构体太鸡肋了,维护迭代也是要额外成本的。
    maigebaoer
        15
    maigebaoer  
       2023-01-10 10:38:53 +08:00 via Android
    @xylophone21 标准库的 read
    bli22ard
        16
    bli22ard  
       2023-01-10 10:46:31 +08:00
    除非你确实需要给调用者返回一个副本,否则就应该使用指针
    allgy
        17
    allgy  
       2023-01-10 10:48:15 +08:00
    别纠结,这个问题不应该是你继续往前走的绊脚石
    aiqinxuancai
        18
    aiqinxuancai  
       2023-01-10 11:02:58 +08:00
    每当接触新语言的时候,总是会遇到,实现这个功能最好的写法最正确的写法是什么的疑问。
    我的总结下来就是,暂时不要过于纠结这些,真的没有必要,一些东西是循环渐进的,这些没必要的担忧只会影响你学习的进度,先实现功能,等过一年,再来看看你的老代码,你应该就知道该怎么做了。
    当年学 C\C++,就是最简单的查找文本,文本对比等等基础方法都一堆,你总不能写的时候先去把每一种都实现实现,再对比对比速度吧,循环几十万次,差个那几 ms ,没必要的。
    summerLast
        19
    summerLast  
       2023-01-10 11:37:24 +08:00
    在 rust 中就不会有这个问题
    777777
        20
    777777  
       2023-01-10 13:12:39 +08:00
    传值,我就不喜欢用指针,因为如果 error 忘了出来,整个程序 nil 空指针会 panic
    hxysnail
        21
    hxysnail  
       2023-01-10 13:13:06 +08:00
    我的项目都是统传递指针,保持简单;另一方面,指针也更加灵活。至于 GC 性能问题,等出现再说,肯定有解决手段,总不能因为性能退回去写汇编吧。项目迭代有一条原则,就是不要过早做性能优化,心里有数就行。
    Trepverter
        22
    Trepverter  
       2023-01-10 13:36:43 +08:00
    OP 能否分享一下 StackOverflow 的英文帖子链接
    ZSeptember
        23
    ZSeptember  
       2023-01-10 13:40:34 +08:00
    写业务,传值,CPU 都不是业务代码的性能瓶颈。
    写业务,最好是减少副作用,防止在上面地方修改了值都不知道,很难排查。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2709 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 11:56 · PVG 19:56 · LAX 03:56 · JFK 06:56
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.