Apr 19, 2017

[Go] Address alignements in Go

Address Alignments in Go

On the not-very-old 32-bit architectures, the documentation guarantees that the first 64-bit word in

  • a global variable or 
  • in an allocated struct or 
  • slice , i.e array
can be relied upon to be 8-byte aligned.
i.e
If a 64-bit word is located at the beginning of the memory block hosting the word,
then the word can be relied upon to be accessed atomically.


An allocated value means the address of the value is the start address of the memory block the value located at.

In other words, the value is placed at the beginning of the memory block it located at.


Be relied upon to be 8-byte aligned doesn't mean the address of the first 64-bit word in a global variable or in an allocated struct or array is always 8-byte aligned on 32-bit architectures. 
It just means if the first 64-bit word is ever accessed atomically, then compilers must guarantee its address is 8-byte aligned at run time.
If it is never accessed atomically, its address may be not 8-byte aligned.

--

type (
 T1 struct {
  v uint64
 }

 T2 struct {
  _ int16
  x T1
  y *T1
 }

 T3 struct {
  _ int16
  x [6]int64
  y *[6]int64
 }
)

// below, the "safe"s in comments mean "safe to be accessed atomically
// on both 64-bit and 32-bit architectures".

var a int64    // a is safe
var b T1       // b.v is safe
var c [6]int64 // c[0] is safe

var d T2 // d.x.v is unsafe
var e T3 // e.x.v[0] is unsafe

func f() {
 var f int64           // f is safe
 var g = []int64{5: 0} // g[0] is safe
 
 var h = e.x[:] // h[0] is unsafe
 
 // here, d.y and e.y are safe, for they are ensured to be 
 // located at the beginning of the memory blocks hosting them.
 d.y = new(T1)
 e.y = &[6]int64{}
 
 _, _, _ = f, g, h
}

// In fact, all elements in c, g and e.y.v are safe to be accessed
// atomically, though the official documentation  doesn't make the
// guarantees.

Code to check/get aligned 8 bytes head:
package mylib

import (
 "unsafe"
 "sync/atomic"
)

type Counter struct {
 x [15]byte // instead of "x uint64"
}

func (c *Counter) xAddr() *uint64 {
 // the return must be 8-byte aligned.
 return (*uint64)(unsafe.Pointer(
  uintptr(unsafe.Pointer(&c.x)) + 8 -
  uintptr(unsafe.Pointer(&c.x))%8))
}

func (c *Counter) Add(delta uint64) {
 p := c.xAddr()
 *p = atomic.AddUint64(p, delta)
}

func (c *Counter) Value() uint64 {
 return atomic.LoadUint64(c.xAddr())
}

Can use x [12]byte instead in go official compiler:
waitgroup.go



Bytes padding example:
// The alignments of type T1 and T2 are the same as
// the largest alignment of their field types (int64),
// 8 on AMD64 OS and 4 on i386 OS.

type T1 struct {
 a int8
 // To make b 8-aligned on AMD64 OS and 4-aligned on i386 OS,
 // 7 bytes padded on AMD64 OS and pad 3 bytes padded on i386 OS here.
 b int64
 c int16
 // To make the size of T1 values is a multiple of the alignment of T1,
 // 6 bytes padded on AMD64 OS and pad 2 bytes padded on i386 OS here.
}
// the sizes of T1 values are 24 on AMD64 OS and 16 on i386 OS.

type T2 struct {
 a int8
 // To make c 2-aligned,
 // 1 byte padded on both AMD64 and i386 OS here.
 c int16
 // To make b 8-aligned on AMD64 OS and 4-aligned on i386 OS,
 // 4 bytes padded on AMD64 OS here. No padding on i386 OS.
 b int64
}
// the sizes of T2 values are 16 on AMD64 OS and 12 on i386 OS.

Final zero size field issue:
http://www.tapirgames.com/blog/golang-unofficial-faq#final-zero-size-field
package main

import (
 "unsafe"
 "fmt"
)

func main() {
 type T1 struct {
  a struct{}
  x int64
 }
 fmt.Println(unsafe.Sizeof(T1{})) // 8
 
 type T2 struct {
  x int64
  a struct{}
 }
 fmt.Println(unsafe.Sizeof(T2{})) // 16
}

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.