首页 > 解决方案 > 这个 Go 方法是否“分配新内存”?

问题描述

(我正在使用 Donovan 和 Kernighan 的The Go Programming Language学习 Go 。这个问题的答案对其他人来说可能是显而易见的,但我很困惑,不知道从哪里开始。)

作为GOPL中的一个练习,作者要求读者修改他们的reverse程序(将ints 的切片反转)“以反转[]byte表示 UTF-8 编码字符串的切片的字符,就地”(93)。他们补充说:“你能在不分配新内存的情况下做到这一点吗?”

简而言之,我想问一下下面是否分配了新的内存。根据打印语句的结果,我认为它不是,但我不确定。让我感到困惑的另一种方法是:如果该方法原地reverse反转,我希望它不会分配新内存。所以,我假设我一定错过了一些东西,因为他们要求该方法在适当的位置工作,然后添加不分配新内存的挑战。他们是在提醒我避免我做过的事情吗?这里是否有一些关于内存分配的额外技巧值得了解(在 Go 中或一般情况下)?

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    phrase := "Hello, 世界!"
    fmt.Printf("Before reverse:\tmemory address %p => phrase: %s\n", &phrase, phrase)
    phrase = string(reverseByRune([]byte(phrase)))
    fmt.Printf("After reverse:\tmemory address %p => phrase: %s\n", &phrase, phrase)
}

func reverseByRune(b []byte) []byte {
    for i := 0; i < len(b); {
        _, size := utf8.DecodeRune(b[i:])
        reverse(b[i : i+size])
        i += size
    }
    reverse(b)
    return b
}

func reverse(b []byte) []byte {
    for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
        b[i], b[j] = b[j], b[i]
    }
    return b
}

它在 Go Playground 上:https: //play.golang.org/p/Qn7nYXLGoQn

PS 我只能投票和接受一次,但如果有人想要额外的感谢,我会喜欢任何关于内存分配的链接(尤其是在 Go 中)。

标签: gomemory-management

解决方案


编辑:可能问题中的实现也不会在堆上分配内存,但建议的修改仍然应该更有效一点。我最初认为问题中的代码来自书中,并且由于缺少本地编译器而无法检查内存分配。

这是我认为应该没有内存分配的实现。

https://play.golang.org/p/N4mkYoiIHvn

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    phrase := "Hello, 世界!"
    fmt.Printf("Before reverse:\tmemory address %p => phrase: %s\n", &phrase, phrase)
    phrase = string(reverseByRune([]byte(phrase)))
    fmt.Printf("After reverse:\tmemory address %p => phrase: %s\n", &phrase, phrase)
}

func reverseByRune(b []byte) []byte {
    reverse := func (i, j int) {
        for ; i < j; i, j = i+1, j-1 {
            b[i], b[j] = b[j], b[i]
        }
    }
    for i := 0; i < len(b); {
        _, size := utf8.DecodeRune(b[i:])
        reverse(i, i+size-1)
        i += size
    }
    reverse(0, len(b)-1)
    return b
}

最初的实现reverse()需要内存来创建b []byte切片,并且还不必要地返回一个值。虽然理论上可以通过returnreverse. 然后编译器可以“猜测”没有人可以保留指向切片的指针,并可以确保切片是在堆栈而不是堆上创建的。但这只是理论上的推测——我不确定 Go 的编译器是否那么聪明。

建议的实现与原始实现相同,但在原始切片内。

当我们谈论“无内存分配”时,我们通常指的是函数内发生的事情,在这种情况下是reverseByRune(). i像&这样分配在堆栈上的局部变量j 不计算在内,因为它们很便宜。

对于给定的方法,这可能是最有效的实现:

https://play.golang.org/p/YOOSZjIWKZ_r

package main

import (
    "fmt"
    "unicode/utf8"
)

func main() {
    phrase := []byte("Hello, 世界!")
    fmt.Printf("Before reverse:\tmemory address %p => phrase: %s\n", &phrase, string(phrase))
    reverseByRune(phrase)
    fmt.Printf("After reverse:\tmemory address %p => phrase: %s\n", &phrase, string(phrase))
}

func reverseByRune(b []byte) {
    for i := 0; i < len(b); {
        _, size := utf8.DecodeRune(b[i:])
        for k, p := i, i+size-1; k < p; k, p = k+1, p-1 {
            b[k], b[p] = b[p], b[k]
        }
        i += size
    }
    for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 {
        b[i], b[j] = b[j], b[i]
    }
}

但这太过分了!


推荐阅读