为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

Go
森栏 2023-02-06 14:46:17
我正在尝试在 Linux 上进行直接 I/O,所以我需要创建内存对齐缓冲区。我复制了一些代码来做到这一点,但我不明白它是如何工作的:package mainimport (    "fmt"    "golang.org/x/sys/unix"    "unsafe"    "yottaStore/yottaStore-go/src/yfs/test/utils")const (    AlignSize = 4096    BlockSize = 4096)// Looks like dark magicfunc 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)
查看完整描述

1 回答

?
沧海一幻觉

TA贡献1824条经验 获得超5个赞

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


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

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


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


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


我们对值使用按位与uintptr(AlignSize-1)。由于AlignSize是 2 的幂(包含1一位后跟零),因此减一的数字是二进制表示中全是 1 的数字,与尾随零的数量一样多AlignSize。看这个例子:


x := 0b1010101110101010101

fmt.Printf("AlignSize   : %22b\n", AlignSize)

fmt.Printf("AlignSize-1 : %22b\n", AlignSize-1)

fmt.Printf("x           : %22b\n", x)

fmt.Printf("result of & : %22b\n", x&(AlignSize-1))

输出:


AlignSize   :          1000000000000

AlignSize-1 :           111111111111

x           :    1010101110101010101

result of & :           110101010101

因此,的结果&是偏移量,如果您从中减去该偏移量AlignSize,您将得到一个地址,其尾随零的数量与AlignSize它本身一样多:结果与 的倍数“对齐” AlignSize。


所以我们将使用file从 开始的切片部分offset,我们只需要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包使用十六进制表示形式打印指针值,前缀为0x. uintptr值打印为整数,使用十进制表示。这些值是相等的:


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

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


编辑#2:


当您用于fmt.Println()调试部分计算时,这可能会改变逃逸分析并可能会改变切片的分配(从堆栈到堆)。这也取决于使用的 Go 版本。不要依赖于你的切片被分配在一个(已经)对齐到AlignSize.


查看完整回答
反对 回复 2023-02-06
  • 1 回答
  • 0 关注
  • 102 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信