我正try 在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函数是如何工作的?
  • 如果我try 对该函数的中间步骤进行fmt.Println次迭代,会得到不同的结果,为什么?我猜是因为观察它改变了它的记忆排列(就像在量子物理学中:D)

Edit: 以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].在围棋中没有指针算术,所以为了做这样的事情,我们必须将指针转换为整数(当然有整数算术).为此,我们必须将指针转换为unsafe.Pointer:所有指针都可以转换为这种类型,unsafe.Pointer可以转换为uintptr(这是一个大到足以存储指针值的未解释位的无符号整数),作为一个整数,我们可以对其执行整数算术.

我们使用值为uintptr(AlignSize-1)的按位AND.由于AlignSize是2的幂(包含一个后面跟着0的1位),所以数字1的小数是一个二进制表示充满1的数,与尾随的0 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中减go 它,你得到的地址的尾随零与AlignSize本身一样多:结果与AlignSize的倍数"对齐".

因此,我们将使用从offset开始的file分片的那部分,我们只需要BlockSize:

file = file[offset : offset+BlockSize]

Edit:

查看try 打印步骤的修改后的代码:我得到如下输出:

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.

Edit #2:

当您使用fmt.Println()调试部分计算时,这可能会更改转义分析,并可能更改片的分配(从堆栈到堆).这也取决于所用的围棋版本.不要依赖于在(已经)与AlignSize对齐的地址上分配您的片.

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

Mix print and fmt.Println and stack growing

why struct arrays comparing has different result

Addresses of slices of empty structs

Go相关问答推荐

在字符串与字符串子切片上使用len进行位转移产生意外输出

链自定义GRPC客户端拦截器/DialOptions

GORM Find方法中缺少字段

如何在Golang中覆盖404

如何找到一个空闲的TPM句柄来保存新的密钥对对象?

Go 中将 int 切片转换为自定义 int 切片指针类型的函数

Secrets Manager Update Secret - Secret String 额外的 JSON 编码

如何在 Golang 中打印 2 列表?

获取 nil 指针类型的 reflect.Value

我如何使用 TOML fixtures 在使用 Go Buffalo 框架的开发环境中为我的数据库 seeder ?

如何在切片增长时自动将切片的新元素添加到函数参数

Golang invopop jsonschema 使用 if/then/else

如何在 Golang 中使用具有相同名称或特定关键字的行或列重新排列/排序 CSV

将接口方法的参数限制为几个允许的 struct ?

函数调用中的类型参数panic

golang jwt.MapClaims 获取用户ID

Golang 有类似 C++ 的 decltype 的东西吗?

不理解切片和指针

如何使用通用字段初始化匿名struct数组

有没有一种方法可以确保传递的值具有使用泛型的某些字段?