使用io.Writer时,避免在golang中分配过多的内存

时间:2016-01-20 17:30:17

标签: memory go profiling heap-memory

我正在开发一个名为redis-mass的Go命令行工具,它将一堆redis命令转换为redis protocol format

第一步是移植node.js版本,几乎完全是Go。我使用ioutil.ReadFile(inputFileName)来获取文件的字符串版本,然后返回编码的字符串作为输出。

当我在一个包含2,000,000个redis命令的文件上运行它时,花了大约8秒,而节点版本大约需要16秒。我猜测它只有两倍的原因是因为它首先将整个文件读入内存,所以我改变了我的编码函数以接受一对(raw io.Reader, enc io.Writer),它看起来像这样:

func EncodeStream(raw io.Reader, enc io.Writer) {
    var args []string
    var length int

    scanner := bufio.NewScanner(raw)

    for scanner.Scan() {
            command := strings.TrimSpace(scanner.Text())
            args = parse(command)
            length = len(args)
            if length > 0 {
                    io.WriteString(enc, fmt.Sprintf("*%d\r\n", length))
                    for _, arg := range args {
                            io.WriteString(enc, fmt.Sprintf("$%d\r\n%s\r\n", len(arg), arg))
                    }
            }
    }
}

然而,这200万行文件需要12秒,所以我使用github.com/pkg/profile查看它是如何使用内存的,看起来内存分配的数量是巨大的:

# Alloc = 3162912
# TotalAlloc = 1248612816
# Mallocs = 46001048
# HeapAlloc = 3162912

我可以约束io.Writer使用固定大小的缓冲区并避免所有这些分配吗?

更一般地说,如何避免此方法中的过多分配? Here's the full source for more context

1 个答案:

答案 0 :(得分:0)

通过使用 []byte 而不是字符串来减少分配。 fmt.Printf 直接输出而不是 fmt.Sprintf 和 io.WriteString。

func EncodeStream(raw io.Reader, enc io.Writer) {
    var args []string
    var length int

    scanner := bufio.NewScanner(raw)

    for scanner.Scan() {
            command := bytes.TrimSpace(scanner.Bytes())
            args = parse(command)
            length = len(args)
            if length > 0 {
                    fmt.Printf(enc, "*%d\r\n", length))
                    for _, arg := range args {
                           fmt.Printf(enc, "$%d\r\n%s\r\n", len(arg), arg))
                    }
            }
    }
}