登录
首页 >  Golang >  Go问答

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

来源:stackoverflow

时间:2024-04-23 14:18:36 180浏览 收藏

积累知识,胜过积蓄金银!毕竟在Golang开发的过程中,会遇到各种各样的问题,往往都是一些细节知识点还没有掌握好而导致的,因此基础知识点的积累是很重要的。下面本文《这段代码如何生成内存对齐的切片?》,就带大家讲解一下知识点,若是你对本文感兴趣,或者是想搞懂其中某个知识点,就请你继续往下看吧~

问题内容

我正在尝试在 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 (这是一个无符号整数,足够大以存储指针值的未解释位) ,作为一个整数,我们可以对其进行整数算术。

我们使用值 uintptr(alignsize-1) 按位 and。由于 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 的倍数“对齐”。

因此,我们将使用从 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的倍数,在二进制中它是110000000000000000010000111000000000000 0

编辑#2:

当您使用 fmt.println() 调试部分计算时,可能会更改逃逸分析并可能更改切片的分配(从堆栈到堆)。这也取决于所使用的 go 版本。不要依赖于在(已经)与 alignsize 对齐的地址分配您的切片。

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

Mix print and fmt.Println and stack growing

why struct arrays comparing has different result

Addresses of slices of empty structs

以上就是《这段代码如何生成内存对齐的切片?》的详细内容,更多关于的资料请关注golang学习网公众号!

声明:本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>