Ответ 1
Есть два случая:
- Локальный срез: будет кэшироваться, а накладные расходы
- Глобальный срез или передан (по ссылке): длина не может быть кеширована и есть служебные данные
Накладные расходы для локальных фрагментов
Для локально определенных фрагментов длина кэшируется, поэтому накладные расходы не выполняются. Вы можете увидеть это в сборке следующей программы:
func generateSlice(x int) []int {
return make([]int, x)
}
func main() {
x := generateSlice(10)
println(len(x))
}
Скомпилированный с go tool 6g -S test.go
, это дает, среди прочего, следующие строки:
MOVQ "".x+40(SP),BX
MOVQ BX,(SP)
// ...
CALL ,runtime.printint(SB)
Что происходит, так это то, что первая строка извлекает длину x
, получая значение, находящееся в 40 байт от начала x
и, самое главное, кэширует это значение в BX
, которое затем используется для каждого случая of len(x)
. Причиной смещения является то, что массив имеет следующую структуру (source):
typedef struct
{ // must not move anything
uchar array[8]; // pointer to data
uchar nel[4]; // number of elements
uchar cap[4]; // allocated number of elements
} Array;
nel
- это то, к чему обращается len()
. Вы можете увидеть это в генерации кода.
Глобальные и ссылочные фрагменты имеют служебные данные
Для общих значений кеширование длины невозможно, поскольку компилятор должен предположить, что срез изменяется между вызовами. Поэтому компилятор должен писать код, который обращается к атрибуту length напрямую каждый раз. Пример:
func accessLocal() int {
a := make([]int, 1000) // local
count := 0
for i := 0; i < len(a); i++ {
count += len(a)
}
return count
}
var ag = make([]int, 1000) // pseudo-code
func accessGlobal() int {
count := 0
for i := 0; i < len(ag); i++ {
count += len(ag)
}
return count
}
Сравнение сборки обеих функций дает решающее различие в том, что как только переменная является глобальной, доступ к атрибуту nel
больше не кэшируется, и накладные расходы времени выполнения:
// accessLocal
MOVQ "".a+8048(SP),SI // cache length in SI
// ...
CMPQ SI,AX // i < len(a)
// ...
MOVQ SI,BX
ADDQ CX,BX
MOVQ BX,CX // count += len(a)
// accessGlobal
MOVQ "".ag+8(SB),BX
CMPQ BX,AX // i < len(ag)
// ...
MOVQ "".ag+8(SB),BX
ADDQ CX,BX
MOVQ BX,CX // count += len(ag)