前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >百万并发「零拷贝」技术系列之Java实现

百万并发「零拷贝」技术系列之Java实现

作者头像
码农神说
发布2020-08-05 11:38:27
6170
发布2020-08-05 11:38:27
举报
文章被收录于专栏:码农神说

上一篇推文中讲解了零拷贝思想在Linux系统中的实现,主要有mmap、sendfile、splice、tee等,但在Java中目前主要实现了mmap和sendfile。

Java I/O的发展史

第一篇的零拷贝的概述中,我们了解到为了降低内核接口调用的复杂度和提高编码效率,高级语言一般都为程序开发者提供了封装的类库,如C语言的标准库、Java的JDK等。

在JDK1.3之前Java的I/O一直比较传统,是采用Stream阻塞模式。在JDK1.4 的发布版中正式引入NIO,加入了缓冲区Buffer和通道Channel的概念,提供了非阻塞的方式。然而JDK1.4主要是为Socket通讯进行的优化,随后在JDK1.7版本中的NIO2不仅增强了文件系统的处理能力,还做到了真正的异步I/O—AIO。

mmap的实现 - MappedByteBuffer

JDK NIO提供的MappedByteBuffer底层就是调用mmap来实现的,FileChannel.map用来建立内存映射关系:把用户空间和内存空间的虚拟内存地址映射到同一块物理内存。mmap对大文件比较合适,对小文件则容易造成内存碎片,反而不是最佳使用场景。

编码示例如下

代码语言:javascript
复制
public void mmap4zeroCopy(String from, String to) throws IOException {
  FileChannel source = null;
  FileChannel destination = null;
  try {
    source = new RandomAccessFile(from, "r").getChannel();
    destination = new RandomAccessFile(to, "rw").getChannel();

    MappedByteBuffer inMappedBuf = 
      source.map(FileChannel.MapMode.READ_ONLY, 0, source.size());

    destination.write(inMappedBuf);
  } finally {
    if (source != null) {
      source.close();
    }
    if (destination != null) {
      destination.close();
    }
  }
}

sendfile的实现 - transferTo

NIO提供的FileChannel.transferTo方法可以直接将一个channel传递给另一个channel,结合上一篇推文看,channel像极了内核缓冲区。

编码示例如下

代码语言:javascript
复制
public void sendfile4zeroCopy(String from, String to) throws IOException{
  FileChannel source = null;
  FileChannel destination = null;
  try {
    source = new FileInputStream(from).getChannel();
    destination = new FileOutputStream(to).getChannel();
    source.transferTo(0, source.size(), destination);
  } finally {
    if (source != null) {
      source.close();
    }
    if (destination != null) {
      destination.close();
    }
  }
}

传统I/O vs mmap vs sendfile

通过实战来对比下传统I/O、mmap、sendfile的性能及在用户空间和内核空间中消耗的CPU时间,代码如下

代码语言:javascript
复制
import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

/**
 * 公众号:码农神说 示例代码
 */
public class JioChannel {
  public static void main(String[] args) {
    JioChannel channel = new JioChannel();
    try {
      if (args.length < 3) {
        System.out.println("usage: JioChannel <source> "+
                    "<destination> <mode>\n");
        return;
      }

      if ("1".equals(args[2])) { //传统方式的复制
        channel.copy(args[0], args[1]);
      } else if ("2".equals(args[2])) { //mmap的方式
        channel.mmap4zeroCopy(args[0], args[1]);
      } else if ("3".equals(args[2])) { //sendfile的方式
        channel.sendfile4zeroCopy(args[0], args[1]);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  /**
   * 传统方式的复制
   *
   * @param from
   * @param to
   * @throws IOException
   */
  public void copy(String from, String to) throws IOException {
    byte[] data = new byte[8 * 1024];
    FileInputStream fis = null;
    FileOutputStream fos = null;
    long bytesToCopy = new File(from).length();
    long bytesCopied = 0;
    try {
      fis = new FileInputStream(from);
      fos = new FileOutputStream(to);

      while (bytesCopied < bytesToCopy) {
        fis.read(data);
        fos.write(data);
        bytesCopied += data.length;
      }
      fos.flush();
    } finally {
      if (fis != null) {
        fis.close();
      }
      if (fos != null) {
        fos.close();
      }
    }
  }

  /**
   * mmap的方式复制
   *
   * @param from
   * @param to
   * @throws IOException
   */
  public void mmap4zeroCopy(String from, String to) throws IOException {
    FileChannel source = null;
    FileChannel destination = null;
    try {
      source = new RandomAccessFile(from, "r").getChannel();
      destination = new RandomAccessFile(to, "rw").getChannel();

      MappedByteBuffer inMappedBuf = 
          source.map(FileChannel.MapMode.READ_ONLY, 0, source.size());

      destination.write(inMappedBuf);
    } finally {
      if (source != null) {
        source.close();
      }
      if (destination != null) {
        destination.close();
      }
    }
  }

  /**
   * sendfile的方式复制文件
   *
   * @param from
   * @param to
   * @throws IOException
   */
  public void sendfile4zeroCopy(String from, String to) throws IOException {
    FileChannel source = null;
    FileChannel destination = null;
    try {
      source = new FileInputStream(from).getChannel();
      destination = new FileOutputStream(to).getChannel();
      source.transferTo(0, source.size(), destination);
    } finally {
      if (source != null) {
        source.close();
      }
      if (destination != null) {
        destination.close();
      }
    }
  }
}

首先进行代码编译java javac JioChannel.java,它的执行方法是JioChannel <source> <destination> <mode>,其中mode值1为传统方式I/O,2为mmap方式I/O,3为sendfile方式I/O。

执行和输出如下(a.zip为130M的压缩文件)

代码语言:javascript
复制
$ time java JioChannel a.zip b.zip 1
real 0m0.199s
user 0m0.090s
sys  0m0.117s

$ time java JioChannel a.zip b.zip 2
real 0m0.172s
user 0m0.074s
sys  0m0.102s
    
$ time java JioChannel a.zip b.zip 3
real 0m0.162s
user 0m0.057s
sys  0m0.108s

user+sys之和是该执行进程的耗费CPU的总时间,可见mmap和sendfile方式效率高于传统方式,而且用户空间user耗费CPU的时间占比总耗费时间也有所降低。

Linux的time命令

time是linux shell内置的命令,它用于统计/测量系统的资源使用情况,如CPU、内存、I/O等,用法如下

代码语言:javascript
复制
time [ -apqvV ] [ -f FORMAT ] [ -o FILE ]
      [ --append ] [ --verbose ] [ --quiet ] [ --portability ]
      [ --format=FORMAT ] [ --output=FILE ] [ --version ]
      [ --help ] COMMAND [ ARGS ]

内存、I/O等资源可参看time手册,不展开叙述。测量CPU的主要角度是其耗费的时间:实际总耗费时间、用户空间和内核空间各自耗费的时间。

  • real:实际总耗费时间,从会话开始到结束,包括其他进程的使用时间和本进程阻塞的时间;
  • user:该执行进程在用户空间耗费的CPU时间;
  • sys:该执行进程在内核空间耗费的CPU时间(CPU耗费在系统调用(system calls)执行上);
  • user + sys:该执行进程实际耗费的CPU总时间,real时间远大于user+sys,因为它不仅包含其他进程消耗的时间,还有文件寻址等时间消耗。

写在最后

虽然JDK没有实现所有的Linux零拷贝模式,但如果能把mmap和sendfile发挥到极致在性能上也能具有非常可观的提升,比如kafka、netty都是以零拷贝而业界瞩目。下一篇推文将简单介绍下kafka、netty的零拷贝思想及实现,这也是面试经常遇到的问题,敬请关注。

End

版权归@码农神说所有,转载须经授权,翻版必究

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码农神说 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档