ByteBuf--Netty的数据容器
570
2022-05-28
文章目录
一、 零拷贝 简介
二、 传统 BIO 数据拷贝分析 ( 4拷贝 4切换 )
三、 mmap 内存映射 ( 3拷贝 4切换 )
四、 sendFile 函数 ( Linux 2.1 优化 ) ( 3拷贝2切换 )
五、 sendFile 函数 ( Linux 2.4 优化 ) ( 2拷贝 2切换 )
一、 零拷贝 简介
零拷贝作用 : 在网络编程中 , 如果要进行性能优化 , 肯定要涉及到零拷贝 ,
使用零拷贝能极大的提升数据传输性能 ;
零拷贝类型 : 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
分析上述代码中数据拷贝次数 , 用户态与内核态状态切换 ;
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 4 4 次拷贝 , 3 3 3 次用户态与内核态之间的状态切换 , 代价很高 ;
① 拷贝次数分析 : 开始时数据存储在 硬盘文件 中 ,
直接内存拷贝 ( Direct Memory Access )
到 内核缓冲区 ,
CPU 拷贝
到 用户缓冲区 ,
CPU 拷贝
到 Socket 缓冲区 ,
直接内存拷贝 ( Direct Memory Access )
到 协议栈 ;
硬盘文件 -> 内核缓冲区 ( 内核空间 ) -> 用户缓冲区 ( 用户空间 ) -> Socket 缓冲区 ( 内核空间 ) -> 协议栈
② 状态改变分析 : 开始运行的是用户应用程序 ,
起始状态肯定是用户态
, 之后将硬盘文件数据拷贝到内核缓冲区后 ,
转为内核态
, 之后又拷贝到了用户缓冲区 ,
转为用户态
; 数据写出到 Socket 缓冲区 ,
又转为内核态 ,
最后再切换成用户态 , 执行后续应用程序代码逻辑 ;
用户态 -> 内核态 -> 用户态 -> 内核态 -> 用户态
[ 1 ] [1] [1] DMA 全称 ( Direct Memory Access ) ,
直接内存拷贝
,
该拷贝通过内存完成
, 不涉及 CPU 参与 ;
三、 mmap 内存映射 ( 3拷贝 4切换 )
将硬盘中的文件映射到 内核缓冲区 , 用户空间中的应用程序也可以访问该 内核缓冲区 中的数据 , 使用这种机制 , 原来的 4 4 4 次数据拷贝减少到了 3 3 3 次 ,
1 . mmap 数据拷贝过程 :
① 硬盘文件 -> 内核缓冲区 : 硬盘文件数据 , DMA 拷贝到 内核缓冲区 中 ,
应用程序可以直接访问该 内核缓冲区中的数据 ;
② 内核缓冲区 -> Socket 缓冲区 : 内核缓冲区 数据 , 通过 CPU 拷贝到 Socket 缓冲区 ;
③ Socket 缓冲区 -> 协议栈 : Socket 缓冲区 数据 , 通过 DMA 拷贝到 协议栈 ;
硬盘文件 -> 内核缓冲区 ( 内核空间 ) -> Socket 缓冲区 ( 内核空间 ) -> 协议栈
2 . mmap 状态切换 : 其状态切换还是 3 3 3 次 ,
由初始状态 用户态 , 在拷贝数据到内核缓冲区时 , 切换成内核态 ,
访问该内核缓冲区数据时 , 又切换成用户态 ,
将数据拷贝到 Socket 缓冲区时 , 切换成内核态 ,
最后再切换成用户态 , 执行后续应用程序代码逻辑 ;
用户态 -> 内核态 -> 用户态 -> 内核态 -> 用户态
四、 sendFile 函数 ( Linux 2.1 优化 ) ( 3拷贝2切换 )
sendFile 是 Linux 提供的函数 , 其实现了由
内核缓冲区
直接将数据拷贝到
Socket 缓冲区
, 该操作直接在内核空间完成 , 不经过用户空间 , 没有用户态参与 , 因此
减少了一次用户态切换 ;
此次优化 , 由原来的 4 4 4 次拷贝 , 3 3 3 次状态切换 , 变成 3 3 3 次拷贝 , 2 2 2 次状态切换 ;
1 . sendFile 函数 数据拷贝分析 :
① 硬盘文件 -> 内核缓冲区 : 硬盘文件数据 , DMA 拷贝到 内核缓冲区 中 ;
② 内核缓冲区 -> Socket 缓冲区 : 内核缓冲区 数据 , 通过 CPU 拷贝到 Socket 缓冲区 ;
③ Socket 缓冲区 -> 协议栈 : Socket 缓冲区 数据 , 通过 DMA 拷贝到 协议栈 ;
硬盘文件 -> 内核缓冲区 ( 内核空间 ) -> Socket 缓冲区 ( 内核空间 ) -> 协议栈
2 . sendFile 函数 状态切换分析 : 其状态切换只有 2 2 2 次 ,
由初始状态 用户态 , 在拷贝数据到内核缓冲区时 , 切换成内核态 ,
在内核态直接将数据拷贝到 Socket 缓冲区时 , 还是处于内核状态 ,
之后拷贝到协议栈时 , 变成用户状态 ;
用户态 -> 内核态 -> 用户态
五、 sendFile 函数 ( Linux 2.4 优化 ) ( 2拷贝 2切换 )
sendFile 是 Linux 提供的函数 , 其在 Linux 2.4 版本中 , 直接将数据从
内核缓冲区
拷贝到
协议栈
中 ;
此次优化 , 由原来的 4 4 4 次拷贝 , 3 3 3 次状态切换 , 变成 2 2 2 次拷贝 , 2 2 2 次状态切换 ;
1 . sendFile 函数 数据拷贝分析 : 全称 DMA 拷贝 , 没有 CPU 拷贝 ;
① 硬盘文件 -> 内核缓冲区 : 硬盘文件数据 , DMA 拷贝到 内核缓冲区 中 ;
② 内核缓冲区 -> -> 协议栈 : 通过 DMA 拷贝 , 将 内核缓冲区 中的数据直接拷贝到 协议栈 ;
硬盘文件 -> 内核缓冲区 ( 内核空间 ) -> 协议栈
2 . sendFile 函数 状态切换分析 : 其状态切换只有 2 2 2 次 ,
由初始状态 用户态 , 在拷贝数据到内核缓冲区时 , 切换成内核态 ,
在内核态直接将数据拷贝到协议栈时 , 变成用户状态 ;
用户态 -> 内核态 -> 用户态
3 . 少量 CPU 拷贝 : 该机制还存在少量的 CPU 拷贝 , 其 对性能的消耗忽略不计 ; 这些 CPU 拷贝操作是从
内核缓冲区
中将数据的长度 ( Length ) , 偏移量 ( Offset ) 拷贝到
Socket 缓冲区
;
Linux Socket编程
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。