`追加`复杂性 [英] `append` complexity

查看:49
本文介绍了`追加`复杂性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个循环在 Go 编程语言中的计算复杂度是多少?

var a []int对于 i := 0 ;我<;我++ {a = 追加(a, i)}

append 是在线性时间内操作(重新分配内存并复制每个 append 上的所有内容),还是在分摊的常量时间内(如许多语言中的向量类的实现方式)?

解决方案

Go 编程语言规范append 内置函数在必要时重新分配.

<块引用>

追加和复制切片

如果 s 的容量不够大,无法容纳附加值,append 分配一个新的、足够大的切片,既适合现有切片元素和附加值.因此,返回的slice 可能引用不同的底层数组.

必要时,用于扩展目标切片的精确算法取决于实现.当前gc编译器算法见Goruntime包中的growslice函数slice.go 源文件.它是摊销的常数时间.

部分地,切片增长量计算如下:

 newcap := old.capdoublecap := newcap + newcap如果上限 >双盖{newcap = 上限} 别的 {如果 old.len <1024 {新帽 = 双帽} 别的 {对于 newcap <帽{新上限 += 新上限/4}}}

附录

Go 编程语言规范 允许该语言的实现者实现 append 多种方式的内置函数.

例如,新的分配只需要足够大".分配的数量可能是简约的,分配最小的必要数量,或者慷慨的,分配超过最小必要的数量,以最小化多次调整大小的成本.Go gc 编译器使用了一个慷慨的动态数组分摊常量时间算法.

以下代码说明了 append 内置函数的两个合法实现.慷慨的常量函数实现了与 Go gc 编译器相同的分摊常量时间算法.parsimonius 变量函数,一旦初始分配被填满,每次都会重新分配和复制所有内容.Go append 函数和 Go gccgo 编译器用作控件.

包主导入fmt"//慷慨的重新分配func 常量(s []int, x ...int) []int {如果 len(s)+len(x) >上限{newcap := len(s) + len(x)m := 上限如果 m+m <新帽{m = 新盖帽} 别的 {为了 {如果 len(s) <1024 {米 += 米} 别的 {米 += 米/4}如果 !(m < newcap) {休息}}}tmp := make([]int, len(s), m)复制(tmp,s)s = tmp}如果 len(s)+len(x) >上限{恐慌(无法访问")}返回 append(s, x...)}//简约的重新分配func 变量(s []int, x ...int) []int {如果 len(s)+len(x) >上限{tmp := make([]int, len(s), len(s)+len(x))复制(tmp,s)s = tmp}如果 len(s)+len(x) >上限{恐慌(无法访问")}返回 append(s, x...)}功能主(){s := []int{0, 1, 2}x := []int{3, 4}fmt.Println("数据", len(s), cap(s), s, len(x), cap(x), x)a, c, v := s, s, s对于我:= 0;我<4096;我++ {a = append(a, x...)c = 常数(c, x...)v = 变量(v, x...)}fmt.Println("追加", len(a), cap(a), len(x))fmt.Println("常量", len(c), cap(c), len(x))fmt.Println("变量", len(v), cap(v), len(x))}

输出:

GC:

数据 3 3 [0 1 2] 2 2 [3 4]附加 8195 9152 2常数 8195 9152 2变量 8195 8195 2

gccgo:

数据 3 3 [0 1 2] 2 2 [3 4]附加 8195 9152 2常数 8195 9152 2变量 8195 8195 2

总而言之,根据实现,一旦初始容量被填满,append 内置函数可能会也可能不会在每次调用时重新分配.

参考文献:

动态数组

摊销分析

<块引用>

追加和复制切片

如果 s 的容量不够大,无法容纳附加值,append 分配一个新的、足够大的切片,既适合现有切片元素和附加值.因此,返回的slice 可能引用不同的底层数组.

附加到切片规范讨论

规范(在提示和 1.0.3 中)指出:

"如果s的容量不足以容纳额外的值,append 分配一个新的、足够大的切片以适合现有切片元素和附加值.就这样返回的切片可能引用不同的底层数组."

这应该是当且仅当"吗?例如,如果我知道我切片的容量足够长,我敢保证我会不改变底层数组?

罗伯派克

是的,你很放心.

运行时 slice.go 源文件>

数组、切片(和字符串):追加"的机制

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)?

解决方案

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'

这篇关于`追加`复杂性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆