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

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

问题描述

  var a [] int 
在Go编程语言中,这个循环的计算复杂度是多少?对于i:= 0;我< n; i ++ {
a = append(a,i)
}



<$> c $ c> append 在线性时间内操作(重新分配内存并复制每个append中的所有内容),或者在分期固定的时间内(像实现许多语言中的vector类一样)? b $ b

解决方案

Go Programming Language Specification 表示如果需要, append 内置函数会重新分配。


追加并复制切片



如果s的容量不足以适应附加值,
append将分配一个新的,足够大的分片,以适合
现有分片元素和附加值。因此,返回的
切片可能引用不同的基础数组。


生成目标切片的精确算法,必要的,对于追加是依赖于实现的。对于当前的 gc 编译器算法,请参阅Go runtime 中的 growslice 函数c $ c> package slice.go 源文件。这是分期不变的时间。



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

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

ADDENDUM



Go Programming Language规范允许该语言的实现者以多种方式实现 append 内置函数。



<例如,新的分配只需要足够大。分配的金额可能是parsimonius,分配最低必要金额或慷慨,分配超过最低必要金额以最小化多次调整大小的成本。 Go gc 编译器使用一个慷慨的动态数组分段常量时间算法。

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

  package main 

导入fmt

//大量重新分配
func常量(s []) int,x ... int)[] int {
if len(s)+ len(x)> cap(s){
newcap:= len(s)+ len(x)
m:= cap(s)
如果m + m < newcap {
m = newcap
} else {
for {
if len(s)< 1024 {
m + = m
} else {
m + = m / 4
}
if!(m break

$ b tmp:= make([] int,len(s),m)
copy(tmp,s)
s = tmp
}
如果len(s)+ len(x)>上限(
panic(unreachable)
}
返回追加(s,x ...)
}

// Parsimonious重新分配
func变量(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
}
如果len(s)+ len(x)>上限(
panic(unreachable)
}
返回追加(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;我< 4096; i ++ {
a = append(a,x ...)
c =常数(c,x ...)
v =变量(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))
}
$ b $输出:

$ g $:
$ b $

  data 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

总而言之,根据实施情况,一旦初始容量被填充,内置追加>函数可能会或可能不会在每次调用时重新分配。



参考文献:

动态数组

摊销分析


添加并复制切片



如果s的容量不足以容纳附加值,则
append 分配一个新的足够大的切片,它适合
现有切片元素和附加值。因此,返回的
切片可能引用不同的基础数组。



附加到分片规范讨论



规范(在tip和1.0.3 )状态:

如果s的容量不足以适应额外的
值,那么 append 分配一个新的,足够大的切片,该切片适合
既有切片元素和附加值。因此,
返回的切片可能引用不同的基础数组。



这应该是如果且仅当?例如,如果我知道我的slice的
容量足够长,我确信我会
不更改底层数组吗?



Rob Pike



是的,你很放心。


运行时 slice.go 源文件

数组,切片(和字符串):'append'的机制


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天全站免登陆