重庆开县网站建设公司,南宁 建网站,南昌seo推广方式,手机网站建设经验目录
一、零拷贝的基本介绍
二、传统IO数据读写的劣势
三、mmap优化
四、sendFile优化
五、 mmap 和 sendFile 的区别
六、零拷贝实战
6.1 传统IO
6.2 NIO中的零拷贝
6.3 运行结果 一、零拷贝的基本介绍 零拷贝是网络编程的关键#xff0c;很多性能优化都离不开。 在…目录
一、零拷贝的基本介绍
二、传统IO数据读写的劣势
三、mmap优化
四、sendFile优化
五、 mmap 和 sendFile 的区别
六、零拷贝实战
6.1 传统IO
6.2 NIO中的零拷贝
6.3 运行结果 一、零拷贝的基本介绍 零拷贝是网络编程的关键很多性能优化都离不开。 在Java程序中常用的零拷贝有mmap(内存映射)和 sendFile。那么他们在OS里到底是怎么样的一个的设计?我们分析mmap和 sendFile这两个零拷贝 另外我们看下NIO中如何使用零拷贝。 二、传统IO数据读写的劣势 下面是Java中传统IO和网络编程的一段代码
File file new File(index.html);
RandomAccessFile raf new RandomAccess(file, rw);byte []arr new byte[(int)file.length()];
raf.read(arr);Socket socket new ServerSocket(8080).accept();
socket.getOutputStream().write(arr); 我们会调用 read 方法读取 index.html 的内容—— 变成字节数组然后调用 write 方法将 index.html 字节流写到 socket 中那么我们调用这两个方法在 OS 底层发生了什么呢这里用一张图片尝试解释这个过程。 上图中上半部分表示用户态和内核态的上下文切换下半部分表示数据复制操作。下面说说他们的步骤
read 调用导致用户态到内核态的一次变化同时第一次复制开始DMADirect Memory Access直接内存存取即不使用 CPU 拷贝数据到内存而是 DMA 引擎传输数据到内存用于解放 CPU 引擎从磁盘读取 index.html 文件并将数据放入到内核缓冲区。发生第二次数据拷贝即将内核缓冲区的数据拷贝到用户缓冲区同时发生了一次用内核态到用户态的上下文切换。发生第三次数据拷贝我们调用 write 方法系统将用户缓冲区的数据拷贝到 Socket 缓冲区。此时又发生了一次用户态到内核态的上下文切换。第四次拷贝数据异步的从 Socket 缓冲区使用 DMA 引擎拷贝到网络协议引擎。这一段不需要进行上下文切换。write 方法返回再次从内核态切换到用户态。可以看出来拷贝流程实在是太多了那我们如何优化流程呢
三、mmap优化 mmap 通过内存映射将文件映射到内核缓冲区同时用户空间可以共享内核空间的数据。这样在进行网络传输时就可以减少内核空间到用户控件的拷贝次数。如下图 user buffer 和 kernel buffer 共享 index.html。如果你想把硬盘的 index.html 传输到网络中再也不用拷贝到用户空间再从用户空间拷贝到 Socket 缓冲区。 现在只需要从内核缓冲区拷贝到 Socket 缓冲区即可这将减少一次内存拷贝从 4 次变成了 3 次但不减少上下文切换次数。 那么还可以再优化吗
四、sendFile优化 Linux 2.1 版本 提供了 sendFile 函数其基本原理如下数据根本不经过用户态直接从内核缓冲区进入到 Socket Buffer同时由于和用户态完全无关就减少了一次上下文切换。 如上图我们进行 sendFile 系统调用时数据被 DMA 引擎从文件复制到内核缓冲区然后调用然后掉一共 write 方法时从内核缓冲区进入到 Socket这时是没有上下文切换的因为在一个用户空间。最后数据从 Socket 缓冲区进入到协议栈。 此时数据经过了 3 次拷贝3 次上下文切换。 那么还能不能再继续优化呢 例如直接从内核缓冲区拷贝到网络协议栈 实际上Linux 在 2.4 版本中做了一些修改避免了从内核缓冲区拷贝到 Socket buffer 的操作直接拷贝到协议栈从而再一次减少了数据拷贝。具体如下图 现在index.html 要从文件进入到网络协议栈只需 2 次拷贝第一次使用 DMA 引擎从文件拷贝到内核缓冲区第二次从内核缓冲区将数据拷贝到网络协议栈内核缓存区只会拷贝CPU拷贝一些 offset 和 length 信息到 SocketBuffer基本无消耗。 等一下不是说零拷贝吗为什么还是要 2 次拷贝 首先我们说零拷贝是从操作系统的角度来说的。因为内核缓冲区之间没有数据是重复的只有 kernel buffer 有一份数据sendFile 2.1 版本实际上有 2 份数据算不上零拷贝。例如我们刚开始的例子内核缓存区和 Socket 缓冲区的数据就是重复的。而零拷贝不仅仅带来更少的数据复制还能带来其他的性能优势例如更少的上下文切换更少的 CPU 缓存伪共享以及无 CPU 校验和计算。
五、 mmap 和 sendFile 的区别
mmap 适合小数据量读写sendFile 适合大文件传输。mmap 需要 4 次上下文切换3 次数据拷贝sendFile 需要 3 次上下文切换最少 2 次数据拷贝。sendFile 可以利用 DMA 方式减少 CPU 拷贝mmap 则不能必须从内核拷贝到 Socket 缓冲区。在这个选择上rocketMQ 在消费消息时使用了 mmap。kafka 使用了 sendFile。
六、零拷贝实战 我们在NIO 上尝试使用传统IO和零拷贝看看区别。 NIO中的transforTo()方法底层使用了零拷贝。在底层源码的注释中是这样解释这个方法的 This method is potentially much more efficient than a simple loop that reads from the source channel and writes to this channel. Many operating systems can transfer bytes directly from the source channel into the filesystem cache without actually copying them.翻译一下 此方法可能比从源通道读取并向此通道写入的简单循环高效得多。许多操作系统可以直接将字节从源通道传输到文件系统缓存中而不需要实际复制它们。 6.1 传统IO 服务端
//java IO 的服务器
public class OldIOServer {public static void main(String[] args) throws Exception {ServerSocket serverSocket new ServerSocket(7001);while (true) {Socket socket serverSocket.accept();DataInputStream dataInputStream new DataInputStream(socket.getInputStream());try {byte[] byteArray new byte[4096];while (true) {int readCount dataInputStream.read(byteArray, 0, byteArray.length);if (-1 readCount) {break;}}} catch (Exception ex) {ex.printStackTrace();}}}
} 客户端
public class OldIOClient {public static void main(String[] args) throws Exception {Socket socket new Socket(localhost, 7001);String fileName protoc-3.6.1-win32.zip;InputStream inputStream new FileInputStream(fileName);DataOutputStream dataOutputStream new DataOutputStream(socket.getOutputStream());byte[] buffer new byte[4096];long readCount;long total 0;long startTime System.currentTimeMillis();while ((readCount inputStream.read(buffer)) 0) {total readCount;dataOutputStream.write(buffer);}System.out.println(发送总字节数 total , 耗时 (System.currentTimeMillis() - startTime));dataOutputStream.close();socket.close();inputStream.close();}
}
6.2 NIO中的零拷贝 服务端
//服务器
public class NewIOServer {public static void main(String[] args) throws Exception {InetSocketAddress address new InetSocketAddress(7001);ServerSocketChannel serverSocketChannel ServerSocketChannel.open();ServerSocket serverSocket serverSocketChannel.socket();serverSocket.bind(address);//创建bufferByteBuffer byteBuffer ByteBuffer.allocate(4096);while (true) {SocketChannel socketChannel serverSocketChannel.accept();int readcount 0;while (-1 ! readcount) {try {readcount socketChannel.read(byteBuffer);}catch (Exception ex) {// ex.printStackTrace();break;}//倒带position 0、mark 作废byteBuffer.rewind(); }}}
} 客户端 transforTo()方法底层使用了零拷贝。
public class NewIOClient {public static void main(String[] args) throws Exception {SocketChannel socketChannel SocketChannel.open();socketChannel.connect(new InetSocketAddress(localhost, 7001));String filename protoc-3.6.1-win32.zip;//得到一个文件channelFileChannel fileChannel new FileInputStream(filename).getChannel();//准备发送long startTime System.currentTimeMillis();//在linux下一个transferTo 方法就可以完成传输//在windows 下 一次调用 transferTo 只能发送8m , 就需要分段传输文件, 而且要注意传输时的位置//transferTo 底层使用到零拷贝long transferCount fileChannel.transferTo(0, fileChannel.size(), socketChannel);System.out.println(发送的总的字节数 transferCount 耗时: (System.currentTimeMillis() - startTime));//关闭fileChannel.close();}
}
6.3 运行结果 我们拷贝的文件大小有900多M传统IO使用60多msNIO零拷贝使用20多ms。