程序员你了解零拷贝吗?
上图是 JAVA 传统的写操作,具体流程:
1、应用发起写操作,OS 进行一次上下文切换(从用户空间切换为内核空间)
2、并且把数据 copy 到内核缓冲区 Socket Buffer,做了一次 CPU Copy
3、内核空间再把数据 copy 到磁盘或其他存储(网卡,进行网络传输),进行了 DMA Copy
4、写入结束后返回,又从内核空间切换到用户空间
传统 IO
我们可以看出传统的 IO 读写操作,总共进行了 4 次上下文切换,4 次 Copy 动作。我们可以看到数据在内核空间和应用空间之间来回复制,其实他们什么都没有做,就是复制而已,这个机制太浪费时间了,而且是浪费的 CPU 的时间。
那我们能不能让数据不要来回复制呢?零拷贝这个技术就是来解决这个问题。关于零拷贝提供了两种解决方式:mmap+write 方式、sendfile 方式
虚拟内存
所有现代操作系统都使用虚拟内存,使用虚拟地址取代物理地址,这样做的好处就是:
1、多个虚拟内存可以指向同一个物理地址
2、虚拟内存空间可以远远大于物理内存空间
我们利用第一条特性可以优化一下上面的设计思路,就是把内核空间和用户空间的虚拟地址映射到同一个物理地址,这样就不需要来回复制了,看图:
mmap+write 方式
使用 mmap+write 方式替换原来的传统 IO 方式,就是利用了虚拟内存的特性,看图
整体流程的核心区别就是,把数据读取到内核缓冲区后,应用程序进行写入操作时,直接是把内核的 Read Buffer 的数据复制到 Socket Buffer 以便进行写入,这次内核之间的复制也是需要 CPU 参与的。
注意:最后把 Socket Buffer 数据拷贝到很多地方,统称 protocol engine(协议引擎)
这个流程就少了一个 CPU Copy,提升了 IO 的速度。不过发现上下文的切换还是 4 次,没有减少,因为还是要应用程序发起 write 操作。那能不能减少上下文切换呢?
sendfile 方式
这种方式可以替换上面的 mmap+write 方式,如:
mmap();write();
替换为
sendfile();
这样就减少了一次上下文切换,因为少了一个应用程序发起 write 操作,直接发起 sendfile 操作。到这里就只有 3 次 Copy,其中只有 1 次 CPU Copy;3 次上下文切换。那能不能把 CPU Copy 减少到没有呢?
gather
Linux2.4 内核进行了优化,提供了 gather 操作,这个操作可以把最后一次 CPU Copy 去除,什么原理呢?就是在内核空间 Read Buffer 和 Socket Buffer 不做数据复制,而是将 Read Buffer 的内存地址、偏移量记录到相应的 Socket Buffer 中,这样就不需要复制(其实本质就是和虚拟内存的解决方法思路一样,就是内存地址的记录),如图:
JAVA 零拷贝
java nio 实现零拷贝,JAVA 提供了一下方法类:
1、MappedByteBuffer2、DirectByteBuffer3、FileChannel.transferTo
具体如何使用,小伙伴们上网自行查阅。
总结
零拷贝技术在很多中间件中,都有利用;如:Kafka,Spark、RocketMQ 等,这个在网络传输数据时,能够提升速度,提升系统性能、吞吐量。小伙伴们不一定会编写,可以先了解基本原理就行。很多好的中间件产品都需要了解一些计算机原理方面的知识,才会更深入的理解。
评论