V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
wangbenjun5
V2EX  ›  程序员

Golang 里面选择 []T 还是 []*T?

  •  
  •   wangbenjun5 · 2020-04-21 13:23:58 +08:00 · 5850 次点击
    这是一个创建于 1714 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我看到公司项目里面无论是方法参数还是返回值全部都是*T,也就是都是指针

    根据很多文章介绍,Go 里面只存在值传递,但是如果作为函数返回值来用,加个指针意义何在?

    func something()([]*T) {
    
    }
    

    经过我测试,加上指针后内存分配的次数反而多了,性能有所下降

    33 条回复    2020-09-23 12:16:44 +08:00
    leoleoasd
        1
    leoleoasd  
       2020-04-21 13:26:10 +08:00
    大结构体防止拷贝?
    Wirbelwind
        2
    Wirbelwind  
       2020-04-21 13:37:40 +08:00
    golang 不是 gc 语言吗 难道不是引用语义?
    unixeno
        4
    unixeno  
       2020-04-21 13:39:34 +08:00 via Android
    大结构体用指针能减少内存拷贝的时间
    maichael
        5
    maichael  
       2020-04-21 13:40:15 +08:00
    我的看法是取决于你的用途。
    wangbenjun5
        6
    wangbenjun5  
    OP
       2020-04-21 13:54:03 +08:00
    @maichael 看了链接里面,最终好像也没什么结论,如果按很多文章的说法,那不如统一都加指针,毕竟指针比非指针“多”一些用途
    keepeye
        7
    keepeye  
       2020-04-21 13:56:26 +08:00
    一直用[]*T 无论 T 多大
    boboliu
        8
    boboliu  
       2020-04-21 13:58:34 +08:00
    你们都不用 interface 么
    fanhed
        9
    fanhed  
       2020-04-21 13:59:54 +08:00
    一般来说, 无脑用*T 就好了
    warlock
        10
    warlock  
       2020-04-21 14:04:02 +08:00
    复杂结构用指针可以减少拷贝 简单结构不用指针可以提升 GC 性能
    matrix67
        11
    matrix67  
       2020-04-21 14:05:07 +08:00
    之前印象笔记收藏的:


    我们通过一些代码示例来演示了在 Go 中值类型和指针类型的一些具体表现,最后我们要回答这么几个问题,希望你能够在使用 Go 编程的过程中更加清晰的掌握这些技巧。Receiver Type 为什么推荐使用指针?

    * 推荐在实例方法上使用指针(前提是这个类型不是一个自定义的 map 、slice 等引用类型)
    * 当结构体较大的时候使用指针会更高效
    * 如果要修改结构内部的数据或状态必须使用指针
    * 当结构类型包含 sync.Mutex 或者同步这种字段时,必须使用指针以避免成员拷贝
    * 如果你不知道该不该使用指针,使用指针!

    “结构较大” 到底多大才算大可能需要自己或团队衡量,如超过 5 个字段或者根据结构体内占用来计算。方法参数该使用什么类型?
    * map 、slice 等类型不需要使用指针(自带 buf )
    * 指针可以避免内存拷贝,结构大的时候不要使用值类型
    * 值类型和指针类型在方法内部都会产生一份拷贝,指向不同
    * 小数据类型如 bool 、int 等没必要使用指针传递
    * 初始化一个新类型时(像 NewEngine() *Engine )使用指针
    * 变量的生命周期越长则使用指针,否则使用值类型

    - 不用指针是值传递,基本就不会走 gc,缺点是导致整个 struct 发生内存拷贝,当然被编译器识别为 inline 函数就什么都不会发生,快得一匹。当你函数输入 /返回参数,struct 就几个 int,float,值传递吧!

    - 指针就是类似引用传递,出作用域会走 gc,当然也不是绝对,比如 inline 函数返回指针就不一定会导致堆分配,当然内置的 new 和 make,map,slice 等本身就分配在堆上就必然走 gc 。

    - gc 对于密集型计算服务的后果就是大量 cpu 计算都消耗在 gc 上,严重影响性能,另外栈内存的分配花销时间可能比堆好

    - 其实在 Go 中,除了方法属主参数,指针使用的并不普遍,使用指针参数的主要目的是在函数内能够更改此指针所引用的值。指针有时候也用来避免较大的值赋值代价。比如将一个指针包裹在接口值中的代价比将一个非接口值包裹到接口值中的代价小得多。另一方面,过多的指针会影响垃圾回收的时长,一般这种情况发生在一个容器中含有大量包含指针的元素。
    lhx2008
        12
    lhx2008  
       2020-04-21 14:06:27 +08:00 via Android
    楼主的例子,你测试的值传递传递是切片,又不是传数组,所以数组根本没被复制。。。和你用不用内部指针就更没关系
    ethego
        13
    ethego  
       2020-04-21 14:10:38 +08:00
    slice 里本来就是指针,你这样写传的是指针的指针。
    lhx2008
        14
    lhx2008  
       2020-04-21 14:10:47 +08:00 via Android
    要说也是 append 进切片的时候会有一次复制,如果你是指针的话复制的东西比较少,但是后面再用的时候都是复制的是切片了。。
    ethego
        15
    ethego  
       2020-04-21 14:11:02 +08:00
    自己用逃逸分析看看就知道了
    whahuzhihao
        16
    whahuzhihao  
       2020-04-21 14:11:50 +08:00
    其实传参时用非指针的 T,能提高 GC 效率
    CEBBCAT
        17
    CEBBCAT  
       2020-04-21 14:13:29 +08:00 via Android
    slice 是两级结构,一层是切片的描述如容量和长度及数组的指针,下层是被上层指向的数组。这样的区别可能是减低数组扩容复制时的时间开销吧,另外这样可能也有一点点加速垃圾回收的考虑?

    我是新手(上海及南方求 Go 坑位)
    ethego
        18
    ethego  
       2020-04-21 14:17:20 +08:00   ❤️ 1
    @lhx2008 只要有 append 进 slice 的操作,被操作实例几乎必逃逸,所以复制也只是复制的指针而已
    zdt3476
        19
    zdt3476  
       2020-04-21 14:22:49 +08:00
    无脑用指针,等需要优化的时候再改就好了。
    simenet
        20
    simenet  
       2020-04-21 14:25:34 +08:00
    别问 问就是无脑 *T
    LANB0
        21
    LANB0  
       2020-04-21 15:07:13 +08:00
    大结构体用指针还可以防止堆栈溢出吧? C 系程序员乱入
    wangbenjun5
        22
    wangbenjun5  
    OP
       2020-04-21 15:10:32 +08:00
    @simenet
    @keepeye
    @fanhed

    哈哈,各位老哥是认真的吗?
    sonxzjw
        23
    sonxzjw  
       2020-04-21 15:18:03 +08:00
    楼主得出内存分配次数更多,性能下降,这个我确实有点疑惑。

    之所以返回指针,也是跟值复制有关的。如果是返回 []T,则函数内会有一个 []T,然后再复制一个 []T 作为返回赋值给调用函数的地方。此时,就有 2 份 []T 了。

    如果返回 []*T 呢?同样也是有 2 份 []*T 的,但此时的 2 份,只是 2 份“引用”而已,嗯...2 份“内存地址”和一份真正存了值的内存。怎么看内存的占用都比 2 份完整的 []T 所占用的内存要小吧

    这是我的回答,如有错误,欢迎指教
    justin2018
        24
    justin2018  
       2020-04-21 15:35:04 +08:00
    一般看 IDE 提示 有问题我就改 😢

    没问题我就用*T 面向 IDE 编程 小白一个 还在摸索~
    wangbenjun5
        25
    wangbenjun5  
    OP
       2020-04-21 15:39:57 +08:00
    @sonxzjw 我不知道是不是因为我测试不严谨,我写了 2 个方法,各返回 100 个 T,一个返回[]T, 一个返回[]*T

    使用 Go 自带 bench 测试,带上了 benchmem,得出的 ns/op 、allocs/op 结果带指针的都大一点,但是 B/op 小一点
    Muninn
        26
    Muninn  
       2020-04-21 16:00:47 +08:00
    我是主要看这个 slice 的使用情况。 如果要 for range 使用,并且这个 *T 作为 receiver 有方法,那就用 []*T,更方便点。 其他情况都用 []T 。
    lewinlan
        27
    lewinlan  
       2020-04-21 20:39:08 +08:00 via Android
    数组总是要遍历的。
    一般来说无脑*T 就可以了。
    把这精神省下来去思考逻辑和架构吧。
    mornlight
        28
    mornlight  
       2020-04-21 21:04:26 +08:00
    @sonxzjw #23 这个理解不对,不管 []T 还是 []*T,都是 slice,返回出去的时候都有一次「拷贝」,只是 slice 的拷贝成本非常低。slice 本质上是这么个东西:
    type slice struct {
    array unsafe.Pointer
    len int
    cap int
    }

    这里的区别是往 []T 填充元素的时候,如果 T 结构体很大,那么使用指针能减少拷贝值的体积。
    monkeyWie
        29
    monkeyWie  
       2020-04-22 08:55:12 +08:00
    看标准库源码里一般返回的是[]*T
    wweir
        30
    wweir  
       2020-04-22 09:54:08 +08:00 via Android
    2,除非明确这玩意要拷贝
    index90
        31
    index90  
       2020-04-22 10:11:13 +08:00
    []*T 有个好处,就是 for range 的时候,可以直接修改里面的元素,而不需要用下标去修改。

    个人觉得没有性能上的差别,更应该站在语义上考虑,一般情况下用[]*T,如果你不想元素被修改(语义上)用[]T
    vlrog
        32
    vlrog  
       2020-04-22 15:06:31 +08:00
    围观大佬们讨论
    wangbenjun5
        33
    wangbenjun5  
    OP
       2020-09-23 12:16:44 +08:00 via Android
    @index90 说得好
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1122 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 18:37 · PVG 02:37 · LAX 10:37 · JFK 13:37
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.