Ломтики структур против ломтиков указателей на структуры
Я часто работаю с фрагментами структур. Вот пример такой структуры:
type MyStruct struct {
val1, val2, val3 int
text1, text2, text3 string
list []SomeType
}
Итак, я определяю свои фрагменты следующим образом:
[]MyStruct
Скажем, у меня около миллиона элементов, и я сильно работаю с срезом:
- Я часто добавляю новые элементы. (Общее количество элементов неизвестно.)
- Я сортирую его время от времени.
- Я также удаляю элементы (хотя и не столько как добавление новых элементов).
- Я часто читаю элементы и передаю их (как аргументы функции).
- Содержимое самих элементов не изменяется.
Мое понимание заключается в том, что это приводит к большому перетасовке фактической структуры. Альтернативой является создание фрагмента указателей на структуру:
[]*MyStruct
Теперь структуры остаются там, где они есть, и мы имеем дело только с указателями, которые, как я предполагаю, имеют меньший размер и, следовательно, делают мои операции быстрее. Но теперь я даю сборщику мусора намного больше работы.
- Можете ли вы предоставить общие рекомендации по работе с структурами напрямую или при работе с указателями на структуры?
- Должен ли я беспокоиться о том, сколько работы я оставляю в GC?
- Является ли служебная нагрузка при копировании структуры или копировании указателя пренебрежимо малой?
- Возможно, миллион элементов не так много. Как все это изменяется, когда срез становится намного больше (но, конечно, все равно в ОЗУ)?
Ответы
Ответ 1
Просто об этом подумал. Ранены некоторые ориентиры:
type MyStruct struct {
F1, F2, F3, F4, F5, F6, F7 string
I1, I2, I3, I4, I5, I6, I7 int64
}
func BenchmarkAppendingStructs(b *testing.B) {
var s []MyStruct
for i := 0; i < b.N; i++ {
s = append(s, MyStruct{})
}
}
func BenchmarkAppendingPointers(b *testing.B) {
var s []*MyStruct
for i := 0; i < b.N; i++ {
s = append(s, &MyStruct{})
}
}
Результаты:
BenchmarkAppendingStructs 1000000 3528 ns/op
BenchmarkAppendingPointers 5000000 246 ns/op
Возьмись: мы в наносекундах. Вероятно, пренебрежимо мало для небольших ломтиков. Но для миллионов ops это разница между миллисекундами и микросекундами.
Btw, я снова попробовал запустить тест с срезами, которые были предварительно выделены (с емкостью 1000000), чтобы устранить накладные расходы из append(), периодически копируя базовый массив. Добавляемые структуры упали на 1000 нс, добавляющие указатели не изменились вообще.
Ответ 2
Можете ли вы предоставить общие рекомендации по работе с структурами напрямую или при работе с указателями на структуры?
Нет, это слишком сильно зависит от всех других факторов, о которых вы уже упоминали.
Единственный реальный ответ: бенчмарк и посмотреть. Каждый случай отличается, и вся теория в мире не имеет никакого значения, когда у вас есть фактические тайминги для работы.
(Тем не менее, моя интуиция будет заключаться в использовании указателей и, возможно, sync.Pool
, чтобы помочь сборщику мусора: http://golang.org/pkg/sync/#Pool)