我正在嘗試在 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))
}
uj5u.com熱心網友回復:
您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位后跟零),因此減一的數字是二進制表示中全為一的數字,與尾隨零的數量一樣多AlignSize。看這個例子:
x := 0b1010101110101010101
fmt.Printf("AlignSize : "b\n", AlignSize)
fmt.Printf("AlignSize-1 : "b\n", AlignSize-1)
fmt.Printf("x : "b\n", x)
fmt.Printf("result of & : "b\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。
有關詳細資訊,請參閱相關問題:
混合列印和 fmt.Println 和堆疊增長
為什么結構陣列比較有不同的結果
空結構切片的地址
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/506499.html
