Опубликовано: 19 мая 2022 г. 21:10 | Автор: echodiv | Категория: Разработка | 👁 1100

Оптимизация структур в GO выравниванием

Вычислительные мощности растут, компьютеры выполняют всё более сложные задачи, оперируют огромными объёмами памяти.

Методы оптимизации работы с памятью становятся всё более высокоуровневыми, но не стоит забывать о простых старых методах, таких как выравнивание структур в памяти.

В этой статье рассмотрено, как сократить используемую память для хранения разных в структурах оптимизируя сами структуры и ускорить доступ к данным структур за счёт уменьшения команд необходимых для этого.

В настоящее время нет оптимизации компиляции для выравнивания структур в 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 полей и не изменить размер структуры.

Мы выравняли в памяти структуру и сократили потребляемую память и доступ к ней стал быстрее, ведь процессору теперь нужно меньше данных доставать из оперативной памяти. И структура стала лежать компактнее.