arrays - 如何在不将所有值归零的情况下初始化一个长 Golang 数组?
问题描述
在 Go 中创建数组时,即使在初始化后立即设置不同的值,例如当值应设置为数组中的索引时,数组似乎也将始终归零。
避免这种情况的一种方法是使用数组文字,例如a = [5]int{0,1,2,3,4}
,但对于长数组来说它变得不切实际。我想知道执行初始化的最佳方法是什么。
令人惊讶的是,命名的返回函数优于大型数组的复合文字初始化。
我创建了以下基准来比较性能:
package main
import "testing"
const N = 1000000
var result [N]int
func arrayLiteral() [N]int {
// Replace the 3 dots with the actual value
// I copy-pasted the output of an other program to do this
return [N]int{0,1,2,3,...,N-1}
}
func arrayLoopNamedReturn() (a [N]int) {
for i := 0; i < N; i++ {
a[i] = i
}
return
}
func arrayLoop() [N]int {
var a [N]int
for i := 0; i < N; i++ {
a[i] = i
}
return a
}
func BenchmarkArrayLoop(b *testing.B) {
var r [N]int
for n := 0; n < b.N; n++ {
r = arrayLoop()
}
result = r
}
func BenchmarkArrayLoopNamedReturn(b *testing.B) {
var r [N]int
for n := 0; n < b.N; n++ {
r = arrayLoopNamedReturn()
}
result = r
}
func BenchmarkArrayLiteral(b *testing.B) {
var r [N]int
for n := 0; n < b.N; n++ {
r = arrayLiteral()
}
result = r
}
结果:
N = 10,000
BenchmarkArrayLoop-8 200000 9041 ns/op
BenchmarkArrayLoopNamedReturn-8 200000 6327 ns/op
BenchmarkArrayLiteral-8 300000 4300 ns/op
N = 100,000
BenchmarkArrayLoop-8 10000 191582 ns/op
BenchmarkArrayLoopNamedReturn-8 20000 76125 ns/op
BenchmarkArrayLiteral-8 20000 62714 ns/op
N = 1,000,000
BenchmarkArrayLoop-8 500 2635713 ns/op
BenchmarkArrayLoopNamedReturn-8 1000 1537282 ns/op
BenchmarkArrayLiteral-8 1000 1854348 ns/op
观察:
我没想到命名返回值会对循环产生影响,我认为编译器肯定会做一些优化。对于 1,000,000,它变得比字面初始化更快。
我期望线性缩放,我不明白为什么对于任何一种方法都不是这种情况。
我不确定如何解释这一点,即使它似乎非常基本。有任何想法吗 ?
编辑: Github 上有一个未解决的问题,抱怨命名返回值不应该有所作为。我还发现这是一个令人惊讶的行为。
解决方案
您的结果与数组大小呈非线性关系的原因是,并非所有获取新填充数组所涉及的操作都与数组大小呈线性关系。例如,您需要内存分配,可选地将分配的内存归零,循环填充数组,并且您必须返回(复制)数组的内存。分配是一个很好的例子,它不应该与大小成线性关系,而且复制内存也不应该是线性的(应该增加,但不是线性的)。
避免冗长的复合文字并询问需要清零的新数组值的一种方法是准备好值,然后将其分配给数组变量。
我的意思是有一个包级变量存储计算/填充的数组(最简单的填充一个简单的循环),当你需要一个填充相同的新数组时,只需分配存储的值:
var cache [N]int
func init() {
for i := range cache {
cache[i] = i
}
}
// If you now need a new array:
var result = cache
// Or re-init an existing array:
result = cache
如果您将此添加到您的基准测试中:
func BenchmarkArrayAssign(b *testing.B) {
var r [N]int
for n := 0; n < b.N; n++ {
r = cache
}
result = r
}
或者简单地说:
func BenchmarkArrayAssign(b *testing.B) {
for n := 0; n < b.N; n++ {
result = cache
}
}
这将比你迄今为止最快的ArrayLoopNamedReturn
两倍(当N = 1_000_000
)。
BenchmarkArrayAssign-4 1000 1104829 ns/op
BenchmarkArrayLoop-4 500 3822005 ns/op
BenchmarkArrayLoopNamedReturn-4 500 2326498 ns/op
推荐阅读
- android - 尝试进行接口类型的成员注入时 Dagger 2 NPE
- laravel - 非 www url 正在重定向到主页
- spring - 如何获取本地到 Spring Boot 以本地化消息?
- c# - 使用 Prism 在 WPF 应用程序中使用 RESX 文件
- java - Amazon Coretto 11、Spring Boot 和 PowerMock - 测试失败
- node.js - 在节点中生成后在子进程中提供输入
- node.js - 异步 / 等待 Node.js https.get
- javascript - 如何将永久数据添加到变量中
- python - 如何计算 poly 中的多个变量?
- excel - 我可以循环这个宏 X 次但仍将一些单元格引用增加 +1 吗?