strings.Builder 内存使用

strings.Builder memory usage

使用这个文件(data file):

package main

import (
   "io/ioutil"
   "time"
)

func main() {
   ioutil.ReadFile("100mb.file")
   time.Sleep(time.Duration(time.Minute))
}

显示我的内存使用量为 107 MB。使用此类似文件:

package main

import (
   "bytes"
   "os"
   "time"
)

func read(path_s string) (bytes.Buffer, error) {
   buf_o := bytes.Buffer{}
   open_o, e := os.Open(path_s)
   if e != nil {
      return buf_o, e
   }
   buf_o.ReadFrom(open_o)
   open_o.Close()
   return buf_o, nil
}

func main() {
   read("100mb.file")
   time.Sleep(time.Duration(time.Minute))
}

内存使用量达到 273 MB。最后这个类似的文件:

package main

import (
   "io"
   "os"
   "strings"
   "time"
)

func read(path_s string) (strings.Builder, error) {
   str_o := strings.Builder{}
   open_o, e := os.Open(path_s)
   if e != nil {
      return str_o, e
   }
   io.Copy(&str_o, open_o)
   open_o.Close()
   return str_o, nil
}

func main() {
   read("100mb.file")
   time.Sleep(time.Duration(time.Minute))
}

内存使用量达到 432 MB。我试着小心并关闭文件 可能的。为什么第二个示例的内存使用率如此之高,尤其是 最后的例子?我可以改变一些东西让他们更接近第一个吗 例如?

ioutil.ReadFile("100mb.file") 获取文件的大小,分配一个 []byte 该大小的字节并将字节放入该切片中。

buf_o.ReadFrom(open_o) 分配一个初始的 []byte 并读入该片。如果 reader 中的数据多于切片中 space 中的数据,则该函数会分配更大的切片,将现有数据复制到该切片并读取更多数据。重复此过程直到 EOF。

函数 ioutil.ReadFile 在内部使用字节 Buffer.ReadFrom。查看 ioutil.ReadFile 实现,了解如何改进 bytes.Buffer 的直接使用。逻辑概要是这样的:

var buf bytes.Buffer

// Open file
f, err := os.Open(path)
if err != nil {
    return &buf, err
}
defer f.Close()

// Get size.
fi, err := f.Stat()
if err != nil {
    return &buf, err
}

// Grow to size of file plus extra slop to ensure no realloc.
buf.Grow(int(fi.Size()) + bytes.MinRead)

_, err := buf.ReadFrom(f)
return &buf, err

strings.Builder 示例与 bytes.Buffer 示例一样多次重新分配内部缓冲区。此外,io.Copy 分配一个缓冲区。在阅读之前,您可以通过 growing the builder to the size of the file 改进 strings.Builder 示例。

这是 strings.Builder 的代码:

var buf strings.Builder

// Open file
f, err := os.Open(path)
if err != nil {
    return &buf, err
}
defer f.Close()

// Get size.
fi, err := f.Stat()
if err != nil {
    return &buf, err
}

buf.Grow(int(fi.Size()))

_, err = io.Copy(&buf, f)
return &buf, err

io.Copy 或其他使用额外缓冲区的代码是必需的,因为 strings.Builder 没有 ReadFrom 方法。 strings.Builder 类型没有 ReadFrom 方法,因为该方法可能会泄漏对内部字节片的支持数组的引用。

根据 Muffin Top 的建议,我采用了第二个示例并添加了这个 在调用 ReadFrom:

之前
stat_o, e := open_o.Stat()
if e != nil {
   return buf_o, e
}
buf_o.Grow(bytes.MinRead + int(stat_o.Size()))

内存下降到107MB,和第一个例子基本一样