douchen9569 2013-03-29 11:37
浏览 14
已采纳

追加复杂度

What is the computational complexity of this loop in the Go programming language?

var a []int
for i := 0 ; i < n ; i++ {
  a = append(a, i)
}

Does append operate in linear time (reallocating memory and copying everything on each append), or in amortized constant time (like the way vector classes in many languages are implemnted)?

  • 写回答

2条回答 默认 最新

  • donglv6960 2013-03-29 12:39
    关注

    The Go Programming Language Specification says that the append built-in function reallocates if necessary.

    Appending to and copying slices

    If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large slice that fits both the existing slice elements and the additional values. Thus, the returned slice may refer to a different underlying array.

    The precise algorithm to grow the target slice, when necessary, for an append is implementation dependent. For the current gc compiler algorithm, see the growslice function in the Go runtime package slice.go source file. It's amortized constant time.

    In part, the amount-to-grow slice computation reads:

        newcap := old.cap
        doublecap := newcap + newcap
        if cap > doublecap {
            newcap = cap
        } else {
            if old.len < 1024 {
                newcap = doublecap
            } else {
                for newcap < cap {
                    newcap += newcap / 4
                }
            }
    }
    

    ADDENDUM

    The Go Programming Language Specification allows implementors of the language to implement the append built-in function in a number of ways.

    For example, new allocations only have to be "sufficiently large". The amount allocated may be parsimonius, allocating the minimum necessary amount, or generous, allocating more than the minimum necessary amount to minimize the cost of resizing many times. The Go gc compiler uses a generous dynamic array amortized constant time algorithm.

    The following code illustrates two legal implementations of the append built-in function. The generous constant function implements the same amortized constant time algorithm as the Go gc compiler. The parsimonius variable function, once the initial allocation is filled, reallocates and copies everything every time. The Go append function and the Go gccgo compiler are used as controls.

    package main
    
    import "fmt"
    
    // Generous reallocation
    func constant(s []int, x ...int) []int {
        if len(s)+len(x) > cap(s) {
            newcap := len(s) + len(x)
            m := cap(s)
            if m+m < newcap {
                m = newcap
            } else {
                for {
                    if len(s) < 1024 {
                        m += m
                    } else {
                        m += m / 4
                    }
                    if !(m < newcap) {
                        break
                    }
                }
            }
            tmp := make([]int, len(s), m)
            copy(tmp, s)
            s = tmp
        }
        if len(s)+len(x) > cap(s) {
            panic("unreachable")
        }
        return append(s, x...)
    }
    
    // Parsimonious reallocation
    func variable(s []int, x ...int) []int {
        if len(s)+len(x) > cap(s) {
            tmp := make([]int, len(s), len(s)+len(x))
            copy(tmp, s)
            s = tmp
        }
        if len(s)+len(x) > cap(s) {
            panic("unreachable")
        }
        return append(s, x...)
    }
    
    func main() {
        s := []int{0, 1, 2}
        x := []int{3, 4}
        fmt.Println("data    ", len(s), cap(s), s, len(x), cap(x), x)
        a, c, v := s, s, s
        for i := 0; i < 4096; i++ {
            a = append(a, x...)
            c = constant(c, x...)
            v = variable(v, x...)
        }
        fmt.Println("append  ", len(a), cap(a), len(x))
        fmt.Println("constant", len(c), cap(c), len(x))
        fmt.Println("variable", len(v), cap(v), len(x))
    }
    

    Output:

    gc:

    data     3 3 [0 1 2] 2 2 [3 4]
    append   8195 9152 2
    constant 8195 9152 2
    variable 8195 8195 2
    

    gccgo:

    data     3 3 [0 1 2] 2 2 [3 4]
    append   8195 9152 2
    constant 8195 9152 2
    variable 8195 8195 2
    

    To summarize, depending on the implementation, once the initial capacity is filled, the append built-in function may or may not reallocate on every call.

    References:

    Dynamic array

    Amortized analysis

    Appending to and copying slices

    If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large slice that fits both the existing slice elements and the additional values. Thus, the returned slice may refer to a different underlying array.

    Append to a slice specification discussion

    The spec (at tip and 1.0.3) states:

    "If the capacity of s is not large enough to fit the additional values, append allocates a new, sufficiently large slice that fits both the existing slice elements and the additional values. Thus, the returned slice may refer to a different underlying array."

    Should this be an "If and only if"? For example, if I know the capacity of my slice is sufficiently long, am I assured that I will not change the underlying array?

    Rob Pike

    Yes you are so assured.

    runtime slice.go source file

    Arrays, slices (and strings): The mechanics of 'append'

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥100 c语言,请帮蒟蒻写一个题的范例作参考
  • ¥15 名为“Product”的列已属于此 DataTable
  • ¥15 安卓adb backup备份应用数据失败
  • ¥15 eclipse运行项目时遇到的问题
  • ¥15 关于#c##的问题:最近需要用CAT工具Trados进行一些开发
  • ¥15 南大pa1 小游戏没有界面,并且报了如下错误,尝试过换显卡驱动,但是好像不行
  • ¥15 没有证书,nginx怎么反向代理到只能接受https的公网网站
  • ¥50 成都蓉城足球俱乐部小程序抢票
  • ¥15 yolov7训练自己的数据集
  • ¥15 esp8266与51单片机连接问题(标签-单片机|关键词-串口)(相关搜索:51单片机|单片机|测试代码)