一、 零拷贝 简介
零拷贝作用 : 在网络编程中 , 如果要进行性能优化 , 肯定要涉及到零拷贝 , 使用零拷贝能极大的提升数据传输性能 ;
零拷贝类型 : mmap ( 内存映射 ) 和 sendFile;
数据角度分析 : 在零拷贝机制中 , 整个数据在内存中只有一份数据 , 非零拷贝机制中 , 内核缓冲区 , 用户缓冲区 , Socket 缓冲区 , 各有一份数据 ;
零拷贝指的是没有 CPU 拷贝 , 都是 DMA ( 直接内存访问 ) 拷贝 ;
零拷贝性能优势 : 没有复制数据带来的内存开销 , 没有 CPU 拷贝 , 直接节省了大量 CPU 计算资源 ;
二、 传统 BIO 数据拷贝分析 ( 4拷贝 4切换 )
传统 BIO 数据拷贝代码示例 :
package kim.hsl.nio.zerocopy; import java.io.FileInputStream; import java.io.IOException; import java.net.Inet4Address; import java.net.InetSocketAddress; import java.net.Socket; public class BIOClientDemo { public static void main(String[] args) { try { // 客户端与服务器端连接过程忽略, 主要是分析数据拷贝过程 Socket socket = new Socket(); InetSocketAddress inetSocketAddress = new InetSocketAddress(Inet4Address.getLocalHost(), 8888); socket.connect(inetSocketAddress); // 分析下面过程中, 数据拷贝次数, 和用户态与内核态的转换次数 // 1. 从文件中读取数据 FileInputStream fileInputStream = new FileInputStream("file.txt"); byte[] buffer = new byte[1024]; // 首先将硬盘中的文件, 进行 DMA 拷贝, 此处对应 read 方法, // 将文件数据从硬盘中拷贝到 内核缓冲区 ( 用户态切换成内核态 ) // 将内核缓冲区中的数据, 通过 CPU 拷贝 方式, 拷贝到 用户缓冲区 ( 内核态切换成用户态 ) int readLen = fileInputStream.read(buffer); // 2. 写出数据到服务器 // 将用户缓冲区中的数据, 再次通过 CPU 拷贝方式, 拷贝到 Socket 缓冲区 ( 用户态切换成内核态 ) // 再次使用 DMA 拷贝, 将 Socket 缓冲区中的数据拷贝到 协议栈 ( Protocol Engine ) 中 socket.getOutputStream().write(buffer, 0, readLen); } catch (IOException e) { e.printStackTrace(); } } }
分析上述代码中数据拷贝次数 , 用户态与内核态状态切换 ;
1 . fileInputStream.read(buffer) 操作数据拷贝及状态转换分析 :
① 硬盘 ( 初始用户态 ) -> 内核缓冲区 ( 内核态 ) : 首先将硬盘中的文件 , 进行 DMA[ 1 ] ^{[1]}
[1]
拷贝 , 此处对应 read 方法 , 将文件数据从硬盘中拷贝到 内核缓冲区 ; ( 用户态切换成内核态 )
② 内核缓冲区 ( 内核态 ) -> 用户缓冲区 ( 用户态 ) : 将内核缓冲区中的数据 , 通过 CPU 拷贝 方式 , 拷贝到 用户缓冲区 ; ( 内核态切换成用户态 )
2 . socket.getOutputStream().write(buffer, 0, readLen) 操作数据拷贝及状态转换分析 :
① 用户缓冲区 ( 用户态 ) -> Socket 缓冲区 ( 内核态 ) : 将用户缓冲区中的数据 , 再次通过 CPU 拷贝 方式 , 拷贝到 Socket 缓冲区 ; ( 用户态切换成内核态 )
② Socket 缓冲区 ( 内核态 ) -> 协议栈 : 再次使用 DMA[ 1 ] ^{[1]}
[1]
拷贝 , 将 Socket 缓冲区中的数据拷贝到 协议栈 ( Protocol Engine ) 中 ;
3 . 总结 : 上述进行了 4 44 次拷贝 , 3 33 次用户态与内核态之间的状态切换 , 代价很高 ;
① 拷贝次数分析 : 开始时数据存储在 硬盘文件 中 , 直接内存拷贝 ( Direct Memory Access ) 到 内核缓冲区 , CPU 拷贝 到 用户缓冲区 , CPU 拷贝 到 Socket 缓冲区 , 直接内存拷贝 ( Direct Memory Access ) 到 协议栈 ;
硬盘文件 -> 内核缓冲区 ( 内核空间 ) -> 用户缓冲区 ( 用户空间 ) -> Socket 缓冲区 ( 内核空间 ) -> 协议栈
② 状态改变分析 : 开始运行的是用户应用程序 , 起始状态肯定是用户态 , 之后将硬盘文件数据拷贝到内核缓冲区后 , 转为内核态 , 之后又拷贝到了用户缓冲区 , 转为用户态 ; 数据写出到 Socket 缓冲区 , 又转为内核态 , 最后再切换成用户态 , 执行后续应用程序代码逻辑 ;
用户态 -> 内核态 -> 用户态 -> 内核态 -> 用户态
[ 1 ] [1][1] DMA 全称 ( Direct Memory Access ) , 直接内存拷贝 , 该拷贝通过内存完成 , 不涉及 CPU 参与 ;