Статьи

Как работает Go lang struct

Это 3-й пост моего эксперимента Go lang. Если вы хотите прочитать предыдущие сообщения, перейдите по ссылке:

это-он-стоит обучение-golang

какие-являются-golang-типы

Структуры — это классные типы. Это позволяет создавать пользовательский тип.

Структура базовая

Структура может быть объявлена ​​так

1
2
3
4
type person struct {
   firstName string
   lastName string
}

это объявляет структуру с 2 полями.

Переменные структуры могут быть объявлены следующим образом:

вар р1 человек

Конструкция var инициализирует значение p1 нулевым значением, поэтому оба строковых поля установлены на «».

Конструкция DOT (.) Используется для доступа к полю.

Как определить структурные переменные.

Пара способов, с помощью которых можно создать переменную.

1
2
3
4
5
6
7
8
var p1 person                                      // Zero value
 var p2 = person{}                                  //Zero value
 p3 := person{firstName: "James", lastName: "Bond"} //Proper initialization
 p4 := person{firstName: "James"}                   //Partial initialization
 
  p5 := new(person) // Using new operator , this returns pointer
 p5.firstName = "James"
 p5.lastName = "Bond"

Сравнение структур

Структуру того же типа можно сравнить с помощью оператора «==»

1
2
3
4
5
6
7
8
9
p1 := person{firstName: "James", lastName: "Bond"}
p2 := person{firstName: "James", lastName: "Bond"}
 
 
if p1 == p2 {
  fmt.Println("Same person found!!!!", p1)
 } else {
  fmt.Println("They are different", p1, p2)
 }

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

Структурное преобразование

Go lang не имеет кастинга. Он поддерживает преобразование и применим к любым типам, а не только к struct.

При преобразовании сохраните ссылку на исходный объект и поместите структуру / макет целевого объекта поверх него, чтобы при преобразовании любые изменения, сделанные в исходном объекте после преобразования, были видны целевому объекту.

Это хорошо для уменьшения накладных расходов памяти, но в целях безопасности это может вызвать большие проблемы, поскольку значения могут волшебным образом меняться от исходного объекта.

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

Давайте рассмотрим пример преобразования структуры.

1
2
3
4
5
6
7
8
9
type person struct {
   firstName string
   lastName string
}
 
type anotherperson struct {
 firstName string
 lastName  string
}


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

1
2
3
4
5
6
p1 := person{firstName: "James", lastName: "Bond"}
anotherp1 := anotherperson{firstName: "James", lastName: "Bond"}
 
 
p1  = anotherp1 //This is compile time error
p1 = person(anotherp1)//This is allowed

Компилятор очень умен, чтобы выяснить, что эти два типа совместимы и преобразование разрешено.

Теперь, если вы идете и вносите изменения в структуру другого человека, например, удаляете поле / новое поле / меняете порядок, тогда это становится несовместимым, и компилятор останавливает это!

Когда он разрешает преобразование, он выделяет новую память для целевой переменной и копирует значение.

Например,

1
2
p1 = person(anotherp1)
anotherp1.lastName = "Lee" // Will have not effect on p1

Как распределяются структуры

Так как это составной тип и понимание структуры памяти структуры очень полезно знать, к какому типу накладных расходов это относится.

Текущий процессор сделает некоторые крутые вещи для быстрого и безопасного чтения / записи.

Выделение памяти будет выровнено по размеру слова базовой платформы (32-битной или 64-битной), а также будет выровнено по размеру типа, например, 4-байтовое значение будет выровнено по 4-байтовому адресу.

Выравнивание очень важно для скорости и правильности.

Давайте рассмотрим пример, чтобы понять это, в 64-битной платформе размер слова составляет 64 бита или 8 байт, поэтому для чтения 1 слова потребуется 1 инструкция.

Go lang struct

Значение, показанное красным, составляет 2 байта, и если значение, показанное красным, выделено в 2 слова (т.е. на границе слова), то для чтения / записи значения потребуется несколько операций, а для записи может потребоваться какая-то синхронизация.

Поскольку значение составляет всего 2 байта, оно может легко поместиться в одно слово, поэтому компилятор попытается выделить это в одном слове:

Go lang struct

Выше распределение оптимизировано для чтения / записи. Структура распределения работает по тому же принципу.

Теперь давайте возьмем пример структуры и посмотрим, какой будет структура памяти:

1
2
3
4
5
6
type layouttest struct {
 b  byte
 v  int32
 f  float64
 v2 int32
}


Макет «layoutouttest» будет выглядеть примерно так:

[1 XX 1 1 1 1 X] [1 1 1 1 XXXX] [1 1 1 1 1 1 1 1] [1 1 1 1 XXXX]


Х — для заполнения.

Чтобы разместить эту структуру и добавить выравнивание по типу данных, потребовалось 4 слова.

Если мы вычислим размер структуры (1 + 4 + 4 + 8 = 17), тогда она должна соответствовать значению в 3 словах (8 * 3 = 24), но потребовалось 4 слова (8 * 4 = 32). Может показаться, что 8 байтов потрачены впустую.

Go дает разработчику полный контроль над расположением памяти. Гораздо более компактная структура может быть создана, чтобы получить до 3-х слов.

1
2
3
4
5
6
type compactlyouttest struct {
 f  float64
 v  int32
 v2 int32
 b  byte
}

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

[1 1 1 1 1 1 1 1] [1 1 1 1 1 1 1 1] [1 XXXXXXX]

При таком расположении меньше места теряется при заполнении, и вы можете испытать желание использовать компактное представление.

Вы не должны делать это по нескольким причинам

— Это нарушает читабельность, потому что связанные поля перемещаются повсюду.

— Память не может быть проблемой, так что это может быть просто из-за оптимизации.

— Процессор очень умный, значения читаются в кешировании, а не в слове, поэтому процессор будет читать несколько слов, и вы никогда не увидите замедления чтения. О том, как работает строка кэша, вы можете прочитать в статье cpu-cache-access-pattern .

— Чрезмерная оптимизация может привести к проблеме ложного совместного использования, прочитайте параллельное счетчик без обмена, чтобы увидеть влияние ложного совместного использования в многопоточном коде.

Так что профилируйте приложение, прежде чем делать какие-либо оптимизации

Go имеет встроенные пакеты для получения деталей выравнивания памяти и другой статической информации типов.

Ниже код дает много деталей о расположении памяти

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
var x layouttest
var y compactyouttest
 
 
 
    fmt.Printf("Int alignment %v \n", unsafe.Alignof(10))
 
    fmt.Printf("Int8 aligment %v \n", unsafe.Alignof(int8(10)))
 
    fmt.Printf("Int16 aligment %v \n", unsafe.Alignof(int16(10)))
 
    fmt.Printf("Layoutest aligment %v ans size is %v \n", unsafe.Alignof(x), unsafe.Sizeof(x))
 
    fmt.Printf("Compactlayouttest aligment %v and size is %v \n", unsafe.Alignof(y), unsafe.Sizeof(y))
 
 
 
    fmt.Printf("Type %v has %v fields and takes %v bytes \n", reflect.TypeOf(x).Name(), reflect.TypeOf(x).NumField(), reflect.TypeOf(x).Size())

Пакет Unsafe &lect дает много внутренних деталей и выглядит так, как будто идея пришла из Java.

Код, используемый в этом блоге, доступен на @ 001-struct github.

Смотрите оригинальную статью здесь: Как работает Go lang struct

Мнения, высказанные участниками Java Code Geeks, являются их собственными.