Go中使用syscall.O_DIRECT标志写文件为何速度更慢?
O_DIRECT写入性能不符合预期问题
我编写了一段名为test.go的测试代码,用于统计两次写入操作的耗时(单位:纳秒):两次操作分别向文件写入相同的字节切片,其中一次打开文件时携带syscall.O_DIRECT标志,另一次不携带该标志。
测试代码如下:
package main import ( "os" "time" "fmt" "strconv" "bytes" "syscall" ) func main() { num, _ := strconv.Atoi(os.Args[1]); writeContent:= bytes.Repeat( []byte("1"), num ); t1:= time.Now().UnixNano(); fd1, err := syscall.Open("abc.txt", syscall.O_WRONLY | syscall.O_DIRECT | syscall.O_TRUNC, 0); if err != nil {panic(err);} syscall.Write(fd1, writeContent); t2:= time.Now().UnixNano(); fmt.Println("sysW1:", t2-t1); t1= time.Now().UnixNano(); fd2, err := syscall.Open("abc.txt", syscall.O_WRONLY | syscall.O_TRUNC, 0); if err != nil {panic(err);} syscall.Write(fd2, writeContent); t2= time.Now().UnixNano(); fmt.Println("sysW2:", t2-t1); }
代码通过go build ./test.go编译后,在Linux命令行执行如下命令运行:
./test 1024
测试预期携带syscall.O_DIRECT标志的写入速度更快,但实际结果显示携带该标志的写入速度比不携带的慢约30倍,输出如下:
sysW1: 1107377 sysW2: 37155
此前认知中syscall.O_DIRECT模式下数据拷贝次数更少,写入速度应当更快,实际结果与预期相反。
注:因在Go Playground中运行该程序时输出结果始终为0,故不提供在线运行链接。
原因解释
- 两组测试的计时维度完全不一致:不带
O_DIRECT的常规写入是缓存异步写入,系统调用只要把数据从用户空间拷贝到内核页缓存就会立刻返回,不会等待数据真正写入物理存储,测得的37155纳秒本质是内存拷贝的开销,实际磁盘IO会在调用返回后由内核异步调度执行。而O_DIRECT的核心语义就是绕过内核页缓存,IO请求会直接下发到块设备,write()调用会阻塞到整个物理写入流程完成才返回,磁盘写入的延迟本来就比内存操作高2~3个数量级,耗时更长是必然结果。 O_DIRECT存在严格的对齐约束:Linux系统下使用O_DIRECT发起IO必须同时满足三个对齐要求:用户态缓冲区的内存起始地址、文件写入偏移、写入数据长度,三者都必须是块设备逻辑块大小(传统机械硬盘为512B,新型4Kn盘为4096B)的整数倍。测试代码里用bytes.Repeat生成的缓冲区没有做专门的内存对齐,内核处理IO时会额外申请一块符合对齐要求的临时缓冲区做数据拷贝,反而比普通写入多了一次内存拷贝开销。- 测试场景完全不匹配
O_DIRECT的适用范围:O_DIRECT是为数据库、分布式存储这类自行实现用户态缓存、需要大块顺序写入、希望避免页缓存双重缓存开销的场景设计的,小数据量写入场景下不会带来任何收益。测试仅写入1KB的极小数据,绕过缓存带来的收益完全覆盖不了块设备单次IO的寻址、传输开销,加上两次写入操作同一个文件,第一次O_DIRECT写入后文件元数据已经进入系统缓存,第二次普通写入的打开、写入流程天然更快,结果偏差会进一步放大。 - 若要做公平的性能对比,常规写入组需要在写完后调用
fsync确保数据真正落盘之后再停止计时,否则两组测试统计的根本不是同一个阶段的耗时,结果没有参考价值。
内容的提问来源于stack exchange,提问作者Aluminate
相关产品推荐
相关产品推荐

