go 中有 memset 的模拟吗?

Is there analog of memset in go?

在 C++ 中,我可以使用 memset:

用一些值初始化一个数组
const int MAX = 1000000;
int is_prime[MAX]

memset(is_prime, 1, sizeof(is_prime))

memset 的作用,粗略地可以描述为用一些值填充数组,但这样做真的非常快。

在 go 中我可以做 is_prime := make([]int, 1000000),但这将创建一个全 0 的切片,以我可以使用 new([1000000]int) 的类似方式,但我无法创建一个 [=26] =] 与所有 1 或任何其他非零元素。

当然我可以使用循环来稍后用值填充它,但是 memset 的主要目的是它比循环快得多。

Go 程序员也有 memset 模拟(将数组初始化为某个非零值的快速方法)吗?

最简单的循环解决方案如下所示:

func memsetLoop(a []int, v int) {
    for i := range a {
        a[i] = v
    }
}

标准库中没有 memset 支持,但我们可以利用高度优化的内置 copy()

与重复copy()

我们可以手动设置第一个元素,使用copy()开始将已经设置的部分复制到未设置的部分;其中已经设置的部分每次都变得越来越大(加倍),因此迭代次数为 log(n):

func memsetRepeat(a []int, v int) {
    if len(a) == 0 {
        return
    }
    a[0] = v
    for bp := 1; bp < len(a); bp *= 2 {
        copy(a[bp:], a[:bp])
    }
}

此解决方案的灵感来自 bytes.Repeat() 的实施。如果您只想创建一个新的 []byte 填充相同的值,您可以使用 bytes.Repeat() 函数。您不能将它用于 []byte 以外的一个或多个现有切片,因为您可以使用提供的 memsetRepeat().

在小切片的情况下memsetRepeat()可能比memsetLoop()慢(但在小切片的情况下并不重要,它会立即运行)。

由于使用了快速 copy(),如果元素数量增加,memsetRepeat() 会更快。

对这 2 个解决方案进行基准测试:

var a = make([]int, 1000) // Size will vary

func BenchmarkLoop(b *testing.B) {
    for i := 0; i < b.N; i++ {
        memsetLoop(a, 10)
    }
}

func BenchmarkRepeat(b *testing.B) {
    for i := 0; i < b.N; i++ {
        memsetRepeat(a, 11)
    }
}

基准测试结果

100 个元素:~1.15 倍快

BenchmarkLoop   20000000                81.6 ns/op
BenchmarkRepeat 20000000                71.0 ns/op

1,000 个元素:~2.5 倍快

BenchmarkLoop    2000000               706 ns/op
BenchmarkRepeat  5000000               279 ns/op

10,000 个元素:~快 2 倍

BenchmarkLoop     200000              7029 ns/op
BenchmarkRepeat   500000              3544 ns/op

100,000 个元素:~1.5 倍快

BenchmarkLoop      20000             70671 ns/op
BenchmarkRepeat    30000             45213 ns/op

最高性能增益大约为 3800-4000 个元素,速度提高了 ~3.2 倍

根据标题为 "optimize memset idiom" 的 this bug,除了使用循环外,在 Go 中没有其他方法可以做到这一点。该问题已于 2013 年 1 月 9 日关闭 post

I consider this fixed. Optimizing non-zero cases isn't very interesting.

We can open another bug if people feel strongly about doing more.

所以解决方案是使用 icza 已经涵盖的循环。

bytes.Repeat 但它也只是使用了一个循环:

func Repeat(b []byte, count int) []byte {
    nb := make([]byte, len(b)*count)
    bp := copy(nb, b)
    for bp < len(nb) {
        copy(nb[bp:], nb[:bp])
        bp *= 2
    }
    return nb
}