Go:如何将 unsafe.Pointer 转换为指向未知长度数组的指针?
Go: how to convert unsafe.Pointer into pointer to array with unknown length?
我正在尝试编写一个 Go 程序,它使用 mmap
将包含 float32
值的非常大的文件映射到内存中。这是我的尝试(受 previous answer 启发,为简洁起见省略了错误处理):
package main
import (
"fmt"
"os"
"syscall"
"unsafe"
)
func main() {
fileName := "test.dat"
info, _ := os.Stat(fileName)
fileSize := info.Size()
n := int(fileSize / 4)
mapFile, _ := os.Open(fileName)
defer mapFile.Close()
mmap, _ := syscall.Mmap(int(mapFile.Fd()), 0, int(fileSize),
syscall.PROT_READ, syscall.MAP_SHARED)
defer syscall.Munmap(mmap)
mapArray := (*[n]float32)(unsafe.Pointer(&mmap[0]))
for i := 0; i < n; i++ {
fmt.Println(mapArray[i])
}
}
失败并显示以下错误消息:
./main.go:21: non-constant array bound n
由于 n
是由文件的长度决定的(在编译时不知道),我不能在转换中用常量值替换 n
。如何将 mmap
转换为 float32
值的数组(或切片)?
您首先转换为具有适合您的数据的静态长度类型的数组,然后将该数组切片为正确的长度和容量。
mapSlice := (*[1 << 30]float32)(unsafe.Pointer(&mmap[0]))[:n:n]
不幸的是,您无法获得指向 array 的指针。这是因为 n
不是常量值(即它是在运行时用 fileSize/4
确定的)。 (注意如果 fileSize
是常量,你可以得到一个数组。)
虽然有安全和不安全的替代品。
安全的,或者有些人可能称之为“正确”的方式 -- 这需要一个副本,但您可以控制字节顺序。这是一个例子:
import (
"encoding/binary"
"bytes"
"unsafe" // optional
)
const SIZE_FLOAT32 = unsafe.Sizeof(float32(0)) // or 4
bufRdr := bytes.NewReader(mmap)
mapSlice := make([]float32, len(mmap)/SIZE_FLOAT32) // = fileSize/4
err := binary.Read(bufRdr, binary.LittleEndian, mapSlice) // could pass &mapSlice instead of mapSlice: same result.
// mapSlice now can be used like the mapArray you wanted.
有几种方法可以不安全地执行此操作,但使用 Go 1.17 非常简单。
mapSlice := unsafe.Slice((*float32)(unsafe.Pointer(&mmap[0])), len(mmap)/SIZE_FLOAT32)
您也可以使用 reflect.SliceHeader
。这里有很多细微差别需要注意以防止垃圾收集器问题:
var mapSlice []float32 // mapSlice := []float32{} also works (important thing is that len and cap are 0)
// newSh and oldSh are here for readability (i.e. inlining these variables is ok, but makes things less readable IMO)
newSh := (*reflect.SliceHeader)(unsafe.Pointer(&mapSlice))
oldSh := (*reflect.SliceHeader)(unsafe.Pointer(&mmap))
// Note: order of assigning Data, Cap, Len is important (due to GC)
newSh.Data = oldSh.Data
newSh.Cap = oldSh.Cap/SIZE_FLOAT32
newSh.Len = oldSh.Len/SIZE_FLOAT32
runtime.KeepAlive(mmap) // ensure `mmap` is not freed up until this point.
我能想到的最终 unsafe
方法在@JimB 的回答中给出了——将 mmap
的 Data
转换为 unsafe.Pointer
,然后将其转换为到一个任意大的数组指针,然后最后将该数组切片指定为所需的大小和容量。
我正在尝试编写一个 Go 程序,它使用 mmap
将包含 float32
值的非常大的文件映射到内存中。这是我的尝试(受 previous answer 启发,为简洁起见省略了错误处理):
package main
import (
"fmt"
"os"
"syscall"
"unsafe"
)
func main() {
fileName := "test.dat"
info, _ := os.Stat(fileName)
fileSize := info.Size()
n := int(fileSize / 4)
mapFile, _ := os.Open(fileName)
defer mapFile.Close()
mmap, _ := syscall.Mmap(int(mapFile.Fd()), 0, int(fileSize),
syscall.PROT_READ, syscall.MAP_SHARED)
defer syscall.Munmap(mmap)
mapArray := (*[n]float32)(unsafe.Pointer(&mmap[0]))
for i := 0; i < n; i++ {
fmt.Println(mapArray[i])
}
}
失败并显示以下错误消息:
./main.go:21: non-constant array bound n
由于 n
是由文件的长度决定的(在编译时不知道),我不能在转换中用常量值替换 n
。如何将 mmap
转换为 float32
值的数组(或切片)?
您首先转换为具有适合您的数据的静态长度类型的数组,然后将该数组切片为正确的长度和容量。
mapSlice := (*[1 << 30]float32)(unsafe.Pointer(&mmap[0]))[:n:n]
不幸的是,您无法获得指向 array 的指针。这是因为 n
不是常量值(即它是在运行时用 fileSize/4
确定的)。 (注意如果 fileSize
是常量,你可以得到一个数组。)
虽然有安全和不安全的替代品。
安全的,或者有些人可能称之为“正确”的方式 -- 这需要一个副本,但您可以控制字节顺序。这是一个例子:
import (
"encoding/binary"
"bytes"
"unsafe" // optional
)
const SIZE_FLOAT32 = unsafe.Sizeof(float32(0)) // or 4
bufRdr := bytes.NewReader(mmap)
mapSlice := make([]float32, len(mmap)/SIZE_FLOAT32) // = fileSize/4
err := binary.Read(bufRdr, binary.LittleEndian, mapSlice) // could pass &mapSlice instead of mapSlice: same result.
// mapSlice now can be used like the mapArray you wanted.
有几种方法可以不安全地执行此操作,但使用 Go 1.17 非常简单。
mapSlice := unsafe.Slice((*float32)(unsafe.Pointer(&mmap[0])), len(mmap)/SIZE_FLOAT32)
您也可以使用 reflect.SliceHeader
。这里有很多细微差别需要注意以防止垃圾收集器问题:
var mapSlice []float32 // mapSlice := []float32{} also works (important thing is that len and cap are 0)
// newSh and oldSh are here for readability (i.e. inlining these variables is ok, but makes things less readable IMO)
newSh := (*reflect.SliceHeader)(unsafe.Pointer(&mapSlice))
oldSh := (*reflect.SliceHeader)(unsafe.Pointer(&mmap))
// Note: order of assigning Data, Cap, Len is important (due to GC)
newSh.Data = oldSh.Data
newSh.Cap = oldSh.Cap/SIZE_FLOAT32
newSh.Len = oldSh.Len/SIZE_FLOAT32
runtime.KeepAlive(mmap) // ensure `mmap` is not freed up until this point.
我能想到的最终 unsafe
方法在@JimB 的回答中给出了——将 mmap
的 Data
转换为 unsafe.Pointer
,然后将其转换为到一个任意大的数组指针,然后最后将该数组切片指定为所需的大小和容量。