垃圾回收和Go中指针的正确使用

垃圾回收和Go中指针的正确使用,第1张

垃圾回收和Go中指针的正确使用

前言:
我在

github.com/icza/gox
库中释放了呈现的字符串池,请参见
stringsx.Pool


首先介绍一些背景。

string
Go中的值由类似struct的小型数据结构表示
reflect.StringHeader

type StringHeader struct {        Data uintptr        Len  int}

因此,基本上传递/复制一个

string
值会传递/复制这个小的struct值,无论的长度如何,它都是2个字
string
。在64位体系结构上,即使只有
string
1000个字符,也只有16个字节。

因此,基本上,

string
值已充当指针。引入另一个指针
*string
只会使用法变得复杂,并且您实际上不会获得任何显着的内存。为了优化内存,请不要使用
*string

它起作用了,我的第一个问题是,以这种方式构建映射后,结果数据结构会怎样?图像URL字符串字段是否会以某种方式保留在内存中,其余结果将被垃圾回收?还是结果数据结构会保留在内存中直到程序结束,因为某些内容指向其成员?

如果您有一个指向结构值字段的指针值,则整个结构将保存在内存中,无法进行垃圾回收。请注意,虽然可以释放为该结构的其他字段保留的内存,但是当前的Go运行时和垃圾回收器不会这样做。因此,要获得最佳的内存使用率,您应该忘记存储结构字段的地址(除非您还需要完整的结构值,但是仍然需要特别注意存储字段地址和切片/数组元素地址)。

这样做的原因是因为用于结构值的内存被分配为连续的段,因此仅保留一个引用的字段将严重破坏可用/可用内存,并使最佳内存管理更加困难和效率降低。对这些区域进行碎片整理还需要复制参考字段的存储区域,这将需要“实时更改”指针值(更改存储地址)。

因此,尽管使用指向

string
值的指针可以为您节省一些内存,但是增加的复杂性和附加的间接寻址使其不值得。

那该怎么办呢?

“最佳”解决方案

因此,最干净的方法是继续使用

string
值。

还有我们之前没有谈到的另一项优化。

您可以通过解组JSON API响应来获得结果。这意味着,如果JSON响应中多次包含相同的URL或标记值,

string
则将为它们创建不同的值。

这是什么意思?如果在JSON响应中两次具有相同的URL,则在进行封送处理后,将有2个不同的

string
值,其中包含2个不同的指针,这些指针指向2个不同的已分配字节序列(否则,字符串内容将是相同的)。该
encoding/json
软件包不
string
进行实习

这是一个小应用程序,可以证明这一点:

var s []stringerr := json.Unmarshal([]byte(`["abc", "abc", "abc"]`), &s)if err != nil {    panic(err)}for i := range s {    hdr := (*reflect.StringHeader)(unsafe.Pointer(&s[i]))    fmt.Println(hdr.Data)}

上面的输出(在Go Playground上尝试):

273760312273760315273760320

我们看到3个不同的指针。它们可能是相同的,因为

string
值是不变的。

json
包不重复检测
string
,因为检测增加了内存和计算开销,这显然是不想要的东西值。但是在我们的案例中,我们追求最佳的内存使用率,因此“初始”的额外计算确实值得获得大的内存增益。

因此,让我们自己进行字符串实习。怎么做?

解组JSON结果之后,在构建

tagToUrlMap
地图时,让
string
我们跟踪遇到的值,如果
string
较早看到了后续值,则只需使用较早的值(其字符串描述符)即可。

这是一个非常简单的字符串内部实现:

var cache = map[string]string{}func interned(s string) string {    if s2, ok := cache[s]; ok {        return s2    }    // New string, store it    cache[s] = s    return s}

让我们在上面的示例代码中测试这个“ interner”:

var s []stringerr := json.Unmarshal([]byte(`["abc", "abc", "abc"]`), &s)if err != nil {    panic(err)}for i := range s {    hdr := (*reflect.StringHeader)(unsafe.Pointer(&s[i]))    fmt.Println(hdr.Data, s[i])}for i := range s {    s[i] = interned(s[i])}for i := range s {    hdr := (*reflect.StringHeader)(unsafe.Pointer(&s[i]))    fmt.Println(hdr.Data, s[i])}

上面的输出(在Go Playground上尝试):

273760312 abc273760315 abc273760320 abc273760312 abc273760312 abc273760312 abc

精彩!如我们所见,在使用

interned()
函数之后,
"abc"
在数据结构中仅使用字符串的单个实例(实际上是第一次出现)。这意味着所有其他实例(假设没有其他人使用它们)可以(并且将来)将正确地进行垃圾收集(由垃圾收集器,在将来的某个时间)。

这里不要忘记的一件事:字符串交互器使用一个

cache
字典,该字典存储所有以前遇到的字符串值。因此,要放开这些字符串,您也应该“清除”此缓存映射,最简单的方法是
nil
为其分配一个值。

事不宜迟,让我们看看我们的解决方案:

result := searchImages()tagToUrlMap := make(map[string][]string)for _, image := range result {    imageURL := interned(image.URL)    for _, tag := range image.Tags {        tagName := interned(tag.Name)        tagToUrlMap[tagName] = append(tagToUrlMap[tagName], imageURL)    }}// Clear the interner cache:cache = nil

要验证结果:

enc := json.NewEnprer(os.Stdout)enc.SetIndent("", "  ")if err := enc.Enpre(tagToUrlMap); err != nil {    panic(err)}

输出为(在Go Playground上尝试):

{  "blue": [    "https://c8.staticflickr.com/4/3707/11603200203_87810ddb43_o.jpg"  ],  "bridge": [    "https://c3.staticflickr.com/1/48/164626048_edeca27ed7_o.jpg"  ],  "forest": [    "https://c8.staticflickr.com/4/3707/11603200203_87810ddb43_o.jpg",    "https://c3.staticflickr.com/1/48/164626048_edeca27ed7_o.jpg"  ],  "ocean": [    "https://c8.staticflickr.com/4/3707/11603200203_87810ddb43_o.jpg"  ],  "river": [    "https://c3.staticflickr.com/1/48/164626048_edeca27ed7_o.jpg"  ],  "water": [    "https://c8.staticflickr.com/4/3707/11603200203_87810ddb43_o.jpg",    "https://c3.staticflickr.com/1/48/164626048_edeca27ed7_o.jpg"  ]}
进一步的内存优化:

我们使用内置

append()
函数将新的图像URL添加到标签中。
append()
可能(通常确实)分配了比需要更大的份额(考虑未来的增长)。经过“构建”过程后,我们可以遍历
tagToUrlMap
地图并将这些切片“修剪”到所需的最低限度。

这是可以做到的:

for tagName, urls := range tagToUrlMap {    if cap(urls) > len(urls) {        urls2 := make([]string, len(urls))        copy(urls2, urls)        tagToUrlMap[tagName] = urls2    }}


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5600349.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-15
下一篇 2022-12-15

发表评论

登录后才能评论

评论列表(0条)

保存