切片的有效分配(上限与长度) [英] Efficient allocation of slices (cap vs length)

查看:61
本文介绍了切片的有效分配(上限与长度)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我正在创建一个切片,我事先知道我想通过连续调用append的for循环使用1e5元素填充该切片:

Assuming I am creating a slice, which I know in advance that I want to populate via a for loop with 1e5 elements via successive calls to append:

// Append 1e5 strings to the slice

for i := 0; i<= 1e5; i++ {
    value := fmt.Sprintf("Entry: %d", i)
    myslice = append(myslice, value)
}

这是初始化切片的更有效方式,原因为何:

which is the more efficient way of initialising the slice and why:

a.声明零个字符串切片?

a. declaring a nil slice of strings?

var myslice []string

b.预先将其长度设置为1e5?

b. setting its length in advance to 1e5?

myslice = make([]string, 1e5)

c.将其长度和容量都设置为1e5?

c. setting both its length and capacity to 1e5?

myslice = make([]string, 1e5, 1e5)

推荐答案

您的bc解决方案是相同的:使用

Your b and c solutions are identical: creating a slice with make() where you don't specify the capacity, the "missing" capacity defaults to the given length.

还请注意,如果您预先创建一个具有一定长度的切片,则不能使用 append() 填充切片,因为它向切片添加了 new 元素,并且不会重用"分配的元素.因此,在这种情况下,您必须使用索引表达式为元素分配值. myslice[i] = value.

Also note that if you create the slice with a length in advance, you can't use append() to populate the slice, because it adds new elements to the slice, and it doesn't "reuse" the allocated elements. So in that case you have to assign values to the elements using an index expression, e.g. myslice[i] = value.

如果从容量为0的切片开始,则每当您添加不适合容量的元素时,都必须分配新的后备阵列,并且必须复制旧"内容,因此解决方案的速度必须较慢本质上.

If you start with a slice with 0 capacity, a new backing array have to be allocated and "old" content have to be copied over whenever you append an element that does not fit into the capacity, so that solution must be slower inherently.

我将定义并考虑以下不同的解决方案(我使用[]int切片来避免fmt.Sprintf()干预/干扰我们的基准测试):

I would define and consider the following different solutions (I use an []int slice to avoid fmt.Sprintf() to intervene / interfere with our benchmarks):

var s []int

func BenchmarkA(b *testing.B) {
    for i := 0; i < b.N; i++ {
        s = nil
        for j := 0; j < 1e5; j++ {
            s = append(s, j)
        }
    }
}

func BenchmarkB(b *testing.B) {
    for i := 0; i < b.N; i++ {
        s = make([]int, 0, 1e5)
        for j := 0; j < 1e5; j++ {
            s = append(s, j)
        }
    }
}

func BenchmarkBLocal(b *testing.B) {
    for i := 0; i < b.N; i++ {
        s := make([]int, 0, 1e5)
        for j := 0; j < 1e5; j++ {
            s = append(s, j)
        }
    }
}

func BenchmarkD(b *testing.B) {
    for i := 0; i < b.N; i++ {
        s = make([]int, 1e5)
        for j := range s {
            s[j] = j
        }
    }
}

注意:我在基准测试中使用程序包级别的变量(BLocal除外),因为在使用局部切片变量时可能会(并且实际上确实会)发生某些优化).

Note: I use package level variables in benchmarks (except BLocal), because some optimization may (and actually do) happen when using a local slice variable).

基准测试结果:

BenchmarkA-4         1000     1081599 ns/op     4654332 B/op     30 allocs/op
BenchmarkB-4         3000      371096 ns/op      802816 B/op      1 allocs/op
BenchmarkBLocal-4   10000      172427 ns/op      802816 B/op      1 allocs/op
BenchmarkD-4        10000      167305 ns/op      802816 B/op      1 allocs/op

A:如您所见,从nil切片开始是最慢的,使用最多的内存和分配空间.

A: As you can see, starting with a nil slice is the slowest, uses the most memory and allocations.

B:使用容量(但长度仍然为0)预分配切片,并使用append:它只需要单个分配,并且速度更快,几乎快了三次.

B: Pre-allocating the slice with capacity (but still 0 length) and using append: it requires only a single allocation and is much faster, almost thrice as fast.

BLocal:请注意,使用局部切片而不是程序包变量时,会发生(编译器)优化,并且速度更快:两倍,几乎与D .

BLocal: Do note that when using a local slice instead of a package variable, (compiler) optimizations happen and it gets a lot faster: twice as fast, almost as fast as D.

D:不使用append(),而是将元素分配给预分配的片,即使在使用非局部变量的情况下,也从各个方面获胜.

D: Not using append() but assigning elements to a preallocated slice wins in every aspect, even when using a non-local variable.

这篇关于切片的有效分配(上限与长度)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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