【问题标题】:Does Go have no real way to shrink a slice? Is that an issue?Go 没有真正的方法来缩小切片吗?这是一个问题吗?
【发布时间】:2013-05-20 20:14:51
【问题描述】:

我已经尝试 Go 一段时间了,这个问题一直困扰着我。假设我在一个切片中构建了一个有点大的数据集(例如,1000 万个 int64)。

package main

import (
    "math"
    "fmt"
)

func main() {
    var a []int64
    var i int64;
    upto := int64(math.Pow10(7))
    for i = 0; i < upto; i++ {
        a = append(a, i)
    }
    fmt.Println(cap(a))
}

但后来我决定我不想要其中的大部分,所以我想最终只得到其中的 10 个。我已经尝试过切片和delete techniques on Go's wiki,但它们似乎都没有减少切片的容量。

所以这就是我的问题:Go 是否没有真正的方法来缩小与 realloc()-ing 类似的切片容量,其大小参数比您之前在 C 中对同一指针的调用更小?这是一个问题吗?应该如何处理?

【问题讨论】:

    标签: go slice


    【解决方案1】:

    Go 没有缩小切片的方法。在大多数情况下,这不是问题,但如果您分析您的内存使用情况并发现您使用的过多,您可以采取一些措施:

    首先,您可以创建所需大小的切片并将数据复制到其中。然后垃圾收集器将释放大片。 Copy built-in

    其次,您可以在每次希望生成大切片时重复使用它,因此您永远不会多次分配它。

    最后,您可以使用1e7 代替math.Pow10(7)

    【讨论】:

    • 为了节省人们的点击次数:copy 的签名是 copy(dst, src),而不是 copy(src, dst)
    【解决方案2】:

    实际上执行一个切片的重新分配:

    a = append([]T(nil), a[:newSize]...) // Thanks to @Dijkstra for pointing out the missing ellipsis.
    

    如果它将newSize 元素复制到一个新的内存位置,或者如果它在realloc(3) 中进行实际的就地 调整大小,则完全由编译器自行决定。您可能想调查当前状态,如果有改进的余地,可能会提出issue

    但是,这可能是一个微优化。性能增强的第一个来源几乎总是选择更好的算法和/或更好的数据结构。使用一个巨大的向量来最终只保留一些项目可能不是内存消耗的最佳选择。

    编辑:以上只是部分正确。编译器不能,在一般情况下,如果有其他指针指向切片的支持数组。因此 realloc 不适用。上面的 sn-p 实际上 保证 执行 'newSize' 元素的副本。对于可能造成的任何混乱,我们深表歉意。

    【讨论】:

    • 在我的系统上,当 newSize 为奇数时,结果容量为 newSize+1。当 newSize 为偶数时,产生的容量为 newSize。
    【解决方案3】:

    此外,您可以在应用程序工作期间重复使用大部分分配的内存,请查看:bufs package

    PS 如果你为更小的切片重新分配新内存,旧内存可能不会同时被释放,它会在垃圾收集器决定时释放。

    【讨论】:

      【解决方案4】:

      让我们看看这个例子:

      func main() {
          s := []string{"A", "B", "C", "D", "E", "F", "G", "H"}
          fmt.Println(s, len(s), cap(s))  // slice, length, capacity
      
          t := s[2:4]
          fmt.Println(t, len(t), cap(t))
      
          u := make([]string, len(t))
          copy(u, t)
          fmt.Println(u, len(u), cap(u))
      }
      

      它产生以下输出:

      [A B C D E F G H] 8 8
      [C D] 2 6
      [C D] 2 2
      

      s 是一个包含 8 个字符串的切片。 t 是保留部分[C D] 的切片。 t 的长度为 2,但由于它使用了与 s 相同的隐藏数组,因此其容量为 6(从“C”到“H”)。问题是:如何拥有一个独立于s 隐藏数组的[C D] 切片?只需创建一个长度为 2 的新字符串切片(切片u)并将t 的内容复制到uu的底层隐藏数组与s的隐藏数组不同。

      最初的问题是:您有一个大切片,然后在其上创建一个新的较小切片。由于较小的切片使用相同的隐藏数组,垃圾收集器不会删除隐藏数组。

      有关更多信息,请参阅本文底部:http://blog.golang.org/go-slices-usage-and-internals

      【讨论】:

        【解决方案5】:

        您可以通过将切片的值重新分配给自身的一部分来做到这一点

          a := []int{1,2,3}
          fmt.Println(len(a), a) // 3 [1 2 3]
        
          a = a[:len(a)-1]
          fmt.Println(len(a), a) //2 [1 2]
        

        【讨论】:

        • 这不会改变切片的容量,这正是 OP 所寻找的。即便如此,OP 仍在寻找一种方法来放弃切片中已填充部分不再使用的内存。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-11-23
        • 2011-07-18
        • 1970-01-01
        • 2015-11-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多