Вычислительные мощности растут, компьютеры выполняют всё более сложные задачи, оперируют огромными объёмами памяти.
Методы оптимизации работы с памятью становятся всё более высокоуровневыми, но не стоит забывать о простых старых методах, таких как выравнивание структур в памяти.
В этой статье рассмотрено, как сократить используемую память для хранения разных в структурах оптимизируя сами структуры и ускорить доступ к данным структур за счёт уменьшения команд необходимых для этого.
В настоящее время нет оптимизации компиляции для выравнивания структур в go, это необходимо делать своими руками.
Начало
Для начала сделаем пустую структуру и посмотрим её размер.
package main import ( "fmt" "unsafe" ) type Empty struct { } func main() { a := &Empty{} b := Empty{} fmt.Println(unsafe.Sizeof(a), "bytes") // 8 bytes fmt.Println(unsafe.Sizeof(b), "bytes") // 0 bytes }
Видно, что сама структура пустая, а указатель на неё занимает 8 байт, это число не поменяется, нас интересует размер самой структуры.
Добавим в структуру два поля типов int32 и bool
package main import ( "fmt" "unsafe" ) type Empty struct { n int32 b bool } func main() { b := Empty{} fmt.Println(unsafe.Sizeof(b)) // 8 bytes }
Теперь размер структуры соответствует 8 байтам, память устроена так, что выделяются равные куски памяти под n и b, хотя они занимают 4 байта и 1 байт соответственно, но операционная система не может выделить меньше 4 байт под поле структуры в этом случае. Если у нас будет int64, то мы увидим другую картину:
package main import ( "fmt" "unsafe" ) type Empty struct { n int64 b bool } func main() { b := Empty{} fmt.Println(unsafe.Sizeof(b)) // 16 bytes }
Структура теперь занимает 16 байт так как int64 требует 8 байт и выделяется равный кусок под b, где фактически используется 1 байт.
Так в чём же проблема? А проблема достаточно проста. Если мы создадим структуру с bool, int64, bool (именно в таком порядке), то что мы должны получить?
package main import ( "fmt" "unsafe" ) type Empty struct {
n bool
n int64 b bool } func main() { b := Empty{} fmt.Println(unsafe.Sizeof(b)) // 24 bytes }
Мы получаем размер в 24 байта, то есть 3 по 8. Фактически мы получаем следующую картину:
где красные - используемые байты, а зеленые - неиспользуемые.
Выравнивание
Кажется что можно поместить второй bool к первому и сократить размер памяти. Давайте попробуем
package main import ( "fmt" "unsafe" ) type Empty struct {
a bool
b bool
n int32 } func main() { b := Empty{} fmt.Println(unsafe.Sizeof(b)) // 16 bytes }
Получаем следующий результат:
Соответственно, мы можем добавить еще 6 bool полей и не изменить размер структуры.
Мы выравняли в памяти структуру и сократили потребляемую память и доступ к ней стал быстрее, ведь процессору теперь нужно меньше данных доставать из оперативной памяти. И структура стала лежать компактнее.