Array vs Slice: скорость доступа
этот вопрос о скорости доступ к элементам массивов и срезов, а не об эффективности передачи их функций в качестве аргументов.
Я бы ожидал массивы будет быстрее, чем фрагментов в большинстве случаев, потому что срез-это структура данных, описывающая непрерывный раздел массива, и поэтому при доступе к элементам среза (косвенно к элементам его базового матрица.)
поэтому я написал небольшой тест, чтобы проверить пакет простых операций. Есть 4 контрольные функции, первые 2 теста a глобальные срез и глобальный массив, другие 2 теста a местные срез и локальный массив:
var gs = make([]byte, 1000) // Global slice
var ga [1000]byte // Global array
func BenchmarkSliceGlobal(b *testing.B) {
for i := 0; i < b.N; i++ {
for j, v := range gs {
gs[j]++; gs[j] = gs[j] + v + 10; gs[j] += v
}
}
}
func BenchmarkArrayGlobal(b *testing.B) {
for i := 0; i < b.N; i++ {
for j, v := range ga {
ga[j]++; ga[j] = ga[j] + v + 10; ga[j] += v
}
}
}
func BenchmarkSliceLocal(b *testing.B) {
var s = make([]byte, 1000)
for i := 0; i < b.N; i++ {
for j, v := range s {
s[j]++; s[j] = s[j] + v + 10; s[j] += v
}
}
}
func BenchmarkArrayLocal(b *testing.B) {
var a [1000]byte
for i := 0; i < b.N; i++ {
for j, v := range a {
a[j]++; a[j] = a[j] + v + 10; a[j] += v
}
}
}
я запускал тест несколько раз, вот типичный вывод (go test -bench .*
):
BenchmarkSliceGlobal 300000 4210 ns/op
BenchmarkArrayGlobal 300000 4123 ns/op
BenchmarkSliceLocal 500000 3090 ns/op
BenchmarkArrayLocal 500000 3768 ns/op
анализ результатов:
доступ к глобальному срезу немного медленнее чем доступ к глобальному массиву, который, как я ожидал:4210
vs 4123
ns / op
но доступ к локальному срезу значительно быстрее, чем доступ к локальному массиву:3090
vs 3768
ns / op
мой вопрос: в чем причина этого?
Примечания
Я попытался изменить следующие вещи, но никто не изменил результат:
- размер массива/среза (пробовал 100, 1000, 10000)
- порядок контрольных функций
- тип элемента массива/среза (пробовал
byte
иint
)
2 ответов
сравнение архитектур amd64 сборки как BenchmarkArrayLocal
и BenchmarkSliceLocal
(слишком долго, чтобы поместиться в этом посте):
версия массива загружает адрес a
из памяти несколько раз, практически на каждой операции доступа к массиву:
LEAQ "".a+1000(SP),BX
в то время как версия slice вычисляется исключительно на регистрах после загрузки один раз из памяти:
LEAQ (DX)(SI*1),BX
это не окончательно, но, вероятно, причина. Причина в том, что оба метода, в противном случае практически идентичный. Еще одна примечательная деталь заключается в том, что версия массива вызывает runtime.duffcopy
, что является довольно длинной процедурой сборки, в то время как версия среза-нет.
Go версии 1.8 может устранить некоторые проверки диапазона, так что разница стала больше.
BenchmarkSliceGlobal-4 500000 3220 ns/op
BenchmarkArrayGlobal-4 1000000 1287 ns/op
BenchmarkSliceLocal-4 1000000 1267 ns/op
BenchmarkArrayLocal-4 1000000 1301 ns/op
для массивов я бы рекомендовал использовать размеры из двух степеней и включать логическую и операцию. Таким образом, вы уверены, что компилятор устраняет проверку. Таким образом var ga [1024]byte
С ga[j & 1023]
.