Java零拷贝探究

首先介绍普通拷贝,其流程图如下:

 

当一个用户线程发起要读取磁盘上的某个文件的请求,其大致流程如上图所示:

    • 用户线程发送系统调用read(),由于read()是系统调用,当前线程切换到内核空间
    • 然后,请求文件数据,文件数据从硬件磁盘缓存到内核空间的缓冲区(Kernel Buffer),通过DMA机制(Direct Memory Access)。
    • 接着,将内核空间缓存的数据复制到用户空间缓存,由内核空间切换到用户空间
    • 最后将用户空间的缓存数据复制到内存,用户线程的read()调用结束。

 

当用户线程发起write请求时,大致流程如下:

    • 用户线程发送系统调用write(),由于write()是系统调用,当前线程切换到内核空间
    • 然后,将需要write的数据从用户空间复制到内核空间的缓冲区(Kernel Buffer)。
    • 接着,将内核空间缓存的数据写入到硬件磁盘上。
    • 最后发送done信号,由内核空间切换到用户空间用户线程的write()调用结束

 

    • 注意:以上的read,write过程各经历了(用户空间 -> 内核空间,内核空间 -> 用户空间)的切换,共计4次上下文切换,以及各一次额外的数据拷贝,(read():磁盘 -> 内核空间 -> 用户空间 -> 调用返回;write():用户空间 -> 内核空间 -> 磁盘 -> 调用返回),这都是额外的调用开销。

 

代码演示:

    1 import java.io.DataInputStream;

    2 import java.io.IOException;

    3 import java.net.ServerSocket;

    4 import java.net.Socket;

    5 

    6 public class NonZeroServer {

    7 

    8     public static void main(String[] args) throws IOException {

    9         ServerSocket serverSocket=new ServerSocket(2233);

   10         while(true) {

   11             Socket socket=serverSocket.accept();

   12             DataInputStream dataInputStream=new DataInputStream(socket.getInputStream());

   13             byte[] byteArray=new byte[4096];

   14             while(true) {

   15                 int readCnt=dataInputStream.read(byteArray,0,byteArray.length);

   16                 if(readCnt==-1) break;

   17             }

   18         }

   19     }

   20 }

 

    1 import java.io.DataOutputStream;

    2 import java.io.File;

    3 import java.io.FileInputStream;

    4 import java.io.IOException;

    5 import java.io.InputStream;

    6 import java.net.Socket;

    7 import java.net.UnknownHostException;

    8 

    9 public class NonZeroCopyClient {

   10 

   11     public static void main(String[] args) throws UnknownHostException, IOException {

   12         Socket socket=new Socket("localhost",2233);

   13         String fileName="e:/onos-tutorial-1.0.0r161-ovf.zip";

   14         InputStream inputStream=new FileInputStream(new File(fileName));

   15         DataOutputStream dataOutputStream=new DataOutputStream(socket.getOutputStream());

   16         byte[] buffer=new byte[4096];

   17         long readCnt=0;

   18         long total=0;

   19         long startTime=System.currentTimeMillis();

   20         while((readCnt=inputStream.read(buffer))!=-1) {

   21             total+=readCnt;

   22             dataOutputStream.write(buffer);

   23         }

   24         System.out.println("send "+total/1000.0+" KB,"+"cost "+(System.currentTimeMillis()-startTime)+" ms");

   25         

   26         dataOutputStream.close();

   27         socket.close();

   28         inputStream.close();

   29     }

   30 

   31 }

 

 

 

下面介绍零拷贝

 

 

上图演示的是操作系统层面的零拷贝(写数据,下图是零拷贝的读数据),相比普通的read,write调用,避免了在用户空间进行缓存,上下文切换有一般的4次变为2次,所有的数据操作都是在内核空间进行的(磁盘 <-> 内核空间 <->  调用返回)。系统调用sendfile(),各操作系统的实现可能不一样,有的操作系统可能不支持零拷贝机制。

Java NIO中的MappedByteBuffer,可以将磁盘上的一个文件映射到内存中(仿佛文件存在于用户空间一样,也能减少系统上下文切换),适合有用户交互的文件读写。

 

代码演示:

   

    1 import java.io.IOException;

    2 import java.net.InetSocketAddress;

    3 import java.net.ServerSocket;

    4 import java.nio.ByteBuffer;

    5 import java.nio.channels.ServerSocketChannel;

    6 import java.nio.channels.SocketChannel;

    7 

    8 public class ZeroCopyServer {

    9 

   10     public static void main(String[] args) throws IOException {

   11         ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();

   12         ServerSocket serverSocket=serverSocketChannel.socket();

   13         serverSocket.setReuseAddress(true);

   14         

   15         serverSocket.bind(new InetSocketAddress(2233));

   16         

   17         ByteBuffer byteBuffer=ByteBuffer.allocate(4096);

   18         while(true) {

   19             SocketChannel socketChannel=serverSocketChannel.accept();

   20             socketChannel.configureBlocking(true);

   21             int readCnt=0;

   22             while(readCnt!=-1) {

   23                 try {

   24                     readCnt=socketChannel.read(byteBuffer);//接受client的数据

   25                 }catch (IOException e) {

   26                     e.printStackTrace();

   27                     byteBuffer.clear();

   28                     break;

   29                 }

   30                 byteBuffer.rewind();//相比flip(),不修改limit,limit始终与capacity值一致

   31             }

   32         }

   33     }

   34 

   35 }

 

    1 import java.io.File;

    2 import java.io.FileInputStream;

    3 import java.io.IOException;

    4 import java.net.InetSocketAddress;

    5 import java.net.UnknownHostException;

    6 import java.nio.channels.FileChannel;

    7 import java.nio.channels.SocketChannel;

    8 

    9 public class ZeroCopyClient {

   10 

   11     public static void main(String[] args) throws UnknownHostException, IOException {

   12         SocketChannel socketChannel=SocketChannel.open();

   13         socketChannel.connect(new InetSocketAddress("localhost",2233));

   14         socketChannel.configureBlocking(true);

   15         String fileName="e:/onos-tutorial-1.0.0r161-ovf.zip";

   16         FileChannel fileChannel=new FileInputStream(new File(fileName)).getChannel();

   17     

   18         long startTime=System.currentTimeMillis();

   19         long total=0;

   20         long transferCnt=0;

   21         while(total<fileChannel.size()) {

   22             transferCnt=fileChannel.transferTo(total, fileChannel.size(), socketChannel);//transferTo()会调用操作系统的零拷贝

   23             total+=transferCnt;

   24         }

   25         

   26         System.out.println("send "+total/1000.0+" KB,"+"cost "+(System.currentTimeMillis()-startTime)+" ms");

   27         

   28 //        System.out.println(fileChannel.size());

   29         fileChannel.close();

   30     

   31     }

   32 

   33 }

 

上述普通拷贝与零拷贝(传输的文件大小为2.49GB),运行结果:

传统IO:send 2504169.281 KB,cost 8414 ms

零拷贝:send 2504169.281 KB,cost 1547 ms

可见零拷贝有着相当大的性能提升。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值