清除截断指针的更快或更慢的方法?

A faster or slower way to clear truncated pointers?

在我最近读到的truncate实现中,作者使用以下方式清除截断的项目:

var nilItems    = make(items, 16)

func (s *items) truncate(index int) {
    var toClear items
    *s, toClear = (*s)[:index], (*s)[index:]
    for len(toClear) > 0 {
        toClear = toClear[copy(toClear, nilItems):]
    }
}

当我需要清除不需要的项目时,我会遍历切片并将项目一项一项地设置为 nil

我已经设置了一个简单的 benchmark 并且 for 循环方式似乎更快。

我想知道用copy批量清算有什么好处。

如@MartinGallagher 所述,您的循环已被编译器识别和优化,而 copy() 版本 "too much stuff" 并且未优化。

如果您更改示例以填充非 nil 指针值,您会看到循环版本落后。也不要在基准循环内分配 (make()),在外部分配,并使用 b.ResetTimer() 排除那个时间。

你也有一个非常小的切片,如果你增加它的大小,差异会更明显:

var x = new(int)

func BenchmarkSetNilOneByOne(b *testing.B) {
    nums := make([]*int, 12800)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        for i := range nums {
            nums[i] = x
        }
    }
}

func BenchmarkSetNilInBulk(b *testing.B) {
    nils := make([]*int, 128)
    for i := range nils {
        nils[i] = x
    }

    orig := make([]*int, 12800)
    var nums []*int
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        nums = orig
        for len(nums) > 0 {
            nums = nums[copy(nums, nils):]
        }
    }
}

基准测试结果:

BenchmarkSetNilOneByOne-4          96571         10626 ns/op
BenchmarkSetNilInBulk-4           266690          4023 ns/op

另请注意,您的 "bulk" 版本还多次将切片 headers 分配给 nums。有一种更快的填充切片的方法:您不需要额外的 "nils" 切片,只需开始填充您的切片,您可以将已填充的部分复制到未填充的部分。这也不需要更改/重新分配给 nums 切片 header。参见 Is there analog of memset in go?