细说Go使用DirectIO
一。DirectIO的要求
在Go语言中,要进行DirectIO(简称DIO),要满足以下要求:
- open()的时候加入O_DIRECT标记
- 缓冲区([]byte)要是512字节内存对齐
- 缓冲区([]byte)的大小要是512的倍数
第一个和第三个要求很容易做到,下面重点看第二个要求如何实现
二。创建一个512字节对齐的缓冲区([]byte)
什么内存对齐
什么是“512字节内存对齐”?
简单来说,就是[]byte的第一个元素的地址能被512整除。
判断一个数能否被另一个数整除,很简单,就是使用取模运算%求余数即可,所以我们可以写出下面的函数用来判断一个地址是否512对齐:
func IsAlign(buf []byte, align int) bool {
return uintptr(unsafe.Pointer(&buf[0])) % uintptr(align) == 0
}
其中uintptr(unsafe.Pointer(xxx))
这段,只是做类型转换,使得xxx能被用来做取模运算而已。
这行代码在数学上没有问题,但是在运行效率上我们可以做的更好:当align参数是2的倍数的时候,求余运算可以简化成&
运算,所以上面的函数可以改进成这样:
func IsAlign(buf []byte, align int) bool {
return uintptr(unsafe.Pointer(&buf[0])) & uintptr(align-1) == 0
}
创建一个512对齐的[]byte
经过上面的努力,我们有个手段可以判断一个地址是否512对齐,接下来我们就要利用它来产生一个512对齐的[]byte
当我们创建一个普通的byte切片的时候,它的开始地址(第一个元素的地址)是随机的:
make([]byte, 128)
要想创建一个指定对齐地址的[]byte,语言层面没有给出办法,所以我们要饶一下:
假设系统给我们产生的地址是A,那么我在[A, A+512)的范围内,挨个地址去寻找,必定能找到一个地址它是512地址对齐的。
所以我们可以写个for循环,不停对A+1,然后判断它是否对齐,再+1,周而复始。
但这样也太没效率了,我们可以改进一下:既然我们能很快算出余数,那么,我让A加上(512-余数),不就直接得到那个对齐的地址来吗?
这就是最终函数:
func MakeAlignBuf(size int, align int) []byte {
buf := make([]byte, size+align)
a := int(uintptr(unsafe.Pointer(&buf[0])) & uintptr(align-1))
offset := 0
if a != 0 {
offset = align - a
}
buf = buf[offset : offset+size]
return buf
}
三。进行directIO
func main() {
file, err := os.OpenFile("/mnt/ext4/dio.txt", syscall.O_DIRECT|os.O_CREATE|os.O_WRONLY, 0644) // 满足DIO要求1
if err != nil {
fmt.Println("OpenFile() failed")
return
}
defer file.Close()
buf := MakeAlignBuf(1024, 512) // 满足DIO要求2和3
if !IsAlign(buf, 512) {
fmt.Println("Buf not aligned")
return
}
copy(buf, []byte("hello world\n"))
_, err = file.Write(buf)
if err != nil {
fmt.Println("write error: ", err.Error())
}
}