2015-05-29 3 views
15

Этот вопрос касается скорости элементов доступа массивов и срезов, а не эффективности их передачи в функции в качестве аргументов.Array vs Slice: скорость доступа

Я бы ожидать массивов быть быстрее, чем ломтиков в большинстве случаев, так как срез представляет собой структуру данных, описывающая непрерывную секцию массива и поэтому может быть дополнительным шагом участвовать при доступе к элементам среза (косвенно - элементы его базового массива).

Итак, я написал небольшой тест, чтобы сравнить партию простых операций. Есть 4 эталонных функций, первые 2 испытания на глобальный срезе и глобальный массив, другие 2 теста местный среза и локальный массив:

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 

Анализ результатов:

Доступ к Glob аль ломтик немного медленнее, чем доступ в глобальный массив, который, как я ожидал:
4210 против 4123 нс/оп

Но доступ к локальному кусочком значительно быстрее, чем доступ к локальному массив:
3090 против 3768 нс/оп

Мой вопрос: В чем причина этого?

Примечания

Я пытался варьируя следующие вещи, но никто не изменил результат:

  • размер массива/секции (пытался 100, 1000, 10000)
  • заказ контрольных функций
  • тип элемента массива/среза (исп. byte и int)
+0

Существует несколько факторов, которые влияют на такие микро-контрольные показатели: проверка границ, распределение (куча, стек), генерация кода, оптимизация заглавных букв и т. Д. Посмотрите на сгенерированный сборник в разных условиях, таких как отключенная проверка привязки или отключенная оптимизация. – Volker

+1

Интересно, что если вы добавите [указатель на массив] (http://play.golang.org/p/nyynQgndDl) к эталонам, вы увидите, что они выполняют примерно так же, как срезы. –

ответ

11

Сравнение the amd64 assembly обоих BenchmarkArrayLocal и BenchmarkSliceLocal (слишком долго, чтобы поместиться в этой должности):

версия массива загружает адрес a из памяти несколько раз, практически на каждой операции массива доступа:

LEAQ "".a+1000(SP),BX 

в то время как версия срезе вычисления исключительно на регистрах после загрузки сразу из памяти:

LEAQ (DX)(SI*1),BX 

Это не является убедительным, но, вероятно, причиной. Причина в том, что оба метода в действительности практически идентичны.Еще одна примечательная деталь заключается в том, что версия массива вызывает runtime.duffcopy, что является довольно продолжительной процедурой сборки, тогда как в версии среза нет.

+0

Но 'runtime.duffcopy()' вызывается только один раз? Видя, что контрольный цикл выполняется полмиллиона раз ('N'), это не должно влиять на результат. – icza

+0

Да, это то, что я думаю. – thwd

+0

Получает ли почти такой же результат в случае глобального массива и фрагмента тот факт, что в этом случае скомпилированный код не включает оптимизацию «реестра» и выполняет ту же загрузку адреса из памяти? – icza

0

Перейти версия 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].

Смежные вопросы