此代码如何生成内存对齐的切片?



我正在尝试在Linux上进行直接I/O,所以我需要创建内存对齐的缓冲区。我复制了一些代码来做到这一点,但我不明白它是如何工作的:

package main
import (
"fmt"
"golang.org/x/sys/unix"
"unsafe"
"yottaStore/yottaStore-go/src/yfs/test/utils"
)
const (
AlignSize = 4096
BlockSize = 4096
)
// Looks like dark magic
func Alignment(block []byte, AlignSize int) int {
return int(uintptr(unsafe.Pointer(&block[0])) & uintptr(AlignSize-1))
}
func main() {
path := "/path/to/file.txt"
fd, err := unix.Open(path, unix.O_RDONLY|unix.O_DIRECT, 0666)
defer unix.Close(fd)
if err != nil {
panic(err)
}
file := make([]byte, 4096*2)
a := Alignment(file, AlignSize)
offset := 0
if a != 0 {
offset = AlignSize - a
}
file = file[offset : offset+BlockSize]

n, readErr := unix.Pread(fd, file, 0)

if readErr != nil {
panic(readErr)
}
fmt.Println(a, offset, offset+utils.BlockSize, len(file))
fmt.Println("Content is: ", string(file))
}

我知道我正在生成一个比我需要的多一倍的切片,然后从中提取一个内存对齐块,但Alignment函数对我来说没有意义。

  • Alignment功能如何工作?
  • 如果我尝试fmt.Println该函数的中间步骤,我会得到不同的结果,为什么?我猜是因为观察它会改变它的记忆排列(就像在量子物理学:D中一样)

编辑: 以fmt.println为例,我不再需要任何对齐方式:

package main
import (
"fmt"
"golang.org/x/sys/unix"
"unsafe"
)
func main() {
path := "/path/to/file.txt"
fd, err := unix.Open(path, unix.O_RDONLY|unix.O_DIRECT, 0666)
defer unix.Close(fd)
if err != nil {
panic(err)
}
file := make([]byte, 4096)
fmt.Println("Pointer: ", &file[0])
n, readErr := unix.Pread(fd, file, 0)
fmt.Println("Return is: ", n)
if readErr != nil {
panic(readErr)
}
fmt.Println("Content is: ", string(file))
}

您的AlignSize的值为 2 的幂。在二进制表示中,它包含一个1位,后跟满零:

fmt.Printf("%b", AlignSize) // 1000000000000

make()分配的切片可能具有或多或少随机的内存地址,由二进制中随机跟随的 1 和 0 组成;或者更准确地说是其后备数组的起始地址。

由于您分配了所需大小的两倍,因此可以保证后备数组将覆盖一个地址空间,该地址位于中间某处,该地址以与AlignSize二进制表示形式一样多的零结尾,并且从此开始在数组中具有BlockSize空间。我们想找到这个地址。

这就是Alignment()函数的作用。它获取带有&block[0]的后备数组的起始地址。在 Go 中没有指针算法,所以为了做这样的事情,我们必须将指针转换为整数(当然有整数算术)。为了做到这一点,我们必须将指针转换为unsafe.Pointer:所有指针都可以转换为此类型,并且unsafe.Pointer可以转换为uintptr(这是一个无符号整数,足以存储指针值的未解释位),作为整数,我们可以在其上执行整数算术。

我们使用按位 AND 和值uintptr(AlignSize-1)。由于AlignSize是 2 的幂(包含一个后跟零的1位),因此少数字 1 是一个二进制表示充满 1 的数字,与AlignSize尾随零一样多。请参阅此示例:

x := 0b1010101110101010101
fmt.Printf("AlignSize   : %22bn", AlignSize)
fmt.Printf("AlignSize-1 : %22bn", AlignSize-1)
fmt.Printf("x           : %22bn", x)
fmt.Printf("result of & : %22bn", x&(AlignSize-1))

输出:

AlignSize   :          1000000000000
AlignSize-1 :           111111111111
x           :    1010101110101010101
result of & :           110101010101

所以&的结果是偏移量,如果你从AlignSize中减去,你会得到一个地址,它的尾随零与AlignSize本身一样多:结果与AlignSize的倍数"对齐"。

所以我们将使用从offset开始的file切片部分,我们只需要BlockSize

file = file[offset : offset+BlockSize]

编辑:

查看您修改的代码尝试打印步骤:我得到如下输出:

Pointer:  0xc0000b6000
Unsafe pointer:  0xc0000b6000
Unsafe pointer, uintptr:  824634466304
Unpersand:  0
Cast to int:  0
Return is:  0
Content is: 

请注意,此处没有任何更改。只需fmt包使用十六进制表示形式打印指针值,前缀为0xuintptr值使用十进制表示形式打印为整数。这些值相等:

fmt.Println(0xc0000b6000, 824634466304) // output: 824634466304 824634466304

另请注意,其余的0,因为在我的情况下0xc0000b6000已经是4096的倍数,在二进制中它是1100000000000000000100001110000000000000

编辑#2:

使用fmt.Println()调试计算的各个部分时,这可能会更改转义分析,并可能更改切片的分配(从堆栈到堆)。这也取决于使用的 Go 版本。不要依赖将切片分配到(已经)与AlignSize对齐的地址。

有关更多详细信息,请参阅相关问题:

混合打印和 fmt。打印和堆栈增长

为什么结构数组比较有不同的结果

空结构片的地址

最新更新