Java零拷贝

1、摘要

java 的zero copy多在网络应用程序中使用。Java的libaries在linux和unix中支持zero copy,关键的api是java.nio.channel.FileChannel的transferTo(),transferFrom()方法。我们可以用这两个方法来把bytes直接从调用它的channel传输到另一个writable byte channel,中间不会使data经过应用程序,以便提高数据转移的效率。

2、介绍

java 的zero copy多在网络应用程序中使用。Java的libaries在linux和unix中支持zero copy,关键的api是java.nio.channel.FileChannel的transferTo(),transferFrom()方法。我们可以用这两个方法来把bytes直接从调用它的channel传输到另一个writable byte channel,中间不会使data经过应用程序,以便提高数据转移的效率。

许多web应用都会向用户提供大量的静态内容,这意味着有很多data从硬盘读出之后,会原封不动的通过socket传输给用户。这种操作看起来可能不会怎么消耗CPU,但是实际上它是低效的:kernal把数据从disk读出来,然后把它传输给user级的application,然后application再次把同样的内容再传回给处于kernal级的socket。这种场景下,application实际上只是作为一种低效的中间介质,用来把disk file的data传给socket。

data每次穿过user-kernel boundary,都会被copy,这会消耗cpu,并且占用RAM的带宽。幸运的是,你可以用一种叫做Zero-Copy的技术来去掉这些无谓的 copy。应用程序用zero copy来请求kernel直接把disk的data传输给socket,而不是通过应用程序传输。Zero copy大大提高了应用程序的性能,并且减少了kernel和user模式的上下文切换

使用kernel buffer做中介(而不是直接把data传到user buffer中)看起来比较低效(多了一次copy)。然而实际上kernel buffer是用来提高性能的。在进行读操作的时候,kernel buffer起到了预读cache的作用。当写请求的data size比kernel buffer的size小的时候,这能够显著的提升性能。在进行写操作时,kernel buffer的存在可以使得写请求完全异步。

悲剧的是,当请求的data size远大于kernel buffer size的时候,这个方法本身变成了性能的瓶颈。因为data需要在disk,kernel buffer,user buffer之间拷贝很多次(每次写满整个buffer)。

而Zero copy正是通过消除这些多余的data copy来提升性能。

3、传统方式及涉及到的上下文切换

通过网络把一个文件传输给另一个程序,在OS的内部,这个copy操作要经历四次user mode和kernel mode之间的上下文切换,甚至连数据都被拷贝了四次,如下图:

具体步骤如下:

  1. read() 调用导致一次从user mode到kernel mode的上下文切换。在内部调用了sys_read() 来从文件中读取data。第一次copy由DMA (direct memory access)完成,将文件内容从disk读出,存储在kernel的buffer中。
  2. 然后请求的数据被copy到user buffer中,此时read()成功返回。调用的返回触发了第二次context switch: 从kernel到user。至此,数据存储在user的buffer中。
  3. send() Socket call 带来了第三次context switch,这次是从user mode到kernel mode。同时,也发生了第三次copy:把data放到了kernel adress space中。当然,这次的kernel buffer和第一步的buffer是不同的buffer。
  4. 最终 send() system call 返回了,同时也造成了第四次context switch。同时第四次copy发生,DMA egine将data从kernel buffer拷贝到protocol engine中。第四次copy是独立而且异步的。

4、zero copy方式及涉及的上下文转换

在linux 2.4及以上版本的内核中(如linux 6或centos 6以上的版本),开发者修改了socket buffer descriptor,使网卡支持 gather operation,通过kernel进一步减少数据的拷贝操作。这个方法不仅减少了context switch,还消除了和CPU有关的数据拷贝。user层面的使用方法没有变,但是内部原理却发生了变化:

transferTo()方法使得文件内容被copy到了kernel buffer,这一动作由DMA engine完成。 没有data被copy到socket buffer。取而代之的是socket buffer被追加了一些descriptor的信息,包括data的位置和长度。然后DMA engine直接把data从kernel buffer传输到protocol engine,这样就消除了唯一的一次需要占用CPU的拷贝操作。

5、零拷贝示例

通过网络把一个文件从client传到server:

 1 /**
 2  * disk-nic零拷贝
 3  */
 4 class ZerocopyServer {
 5     ServerSocketChannel listener = null;
 6
 7     protected void mySetup() {
 8         InetSocketAddress listenAddr = new InetSocketAddress(9026);
 9
10         try {
11             listener = ServerSocketChannel.open();
12             ServerSocket ss = listener.socket();
13             ss.setReuseAddress(true);
14             ss.bind(listenAddr);
15             System.out.println("监听的端口:" + listenAddr.toString());
16         } catch (IOException e) {
17             System.out.println("端口绑定失败 : " + listenAddr.toString() + " 端口可能已经被使用,出错原因: " + e.getMessage());
18             e.printStackTrace();
19         }
20
21     }
22
23     public static void main(String[] args) {
24         ZerocopyServer dns = new ZerocopyServer();
25         dns.mySetup();
26         dns.readData();
27     }
28
29     private void readData() {
30         ByteBuffer dst = ByteBuffer.allocate(4096);
31         try {
32             while (true) {
33                 SocketChannel conn = listener.accept();
34                 System.out.println("创建的连接: " + conn);
35                 conn.configureBlocking(true);
36                 int nread = 0;
37                 while (nread != -1) {
38                     try {
39                         nread = conn.read(dst);
40                     } catch (IOException e) {
41                         e.printStackTrace();
42                         nread = -1;
43                     }
44                     dst.rewind();
45                 }
46             }
47         } catch (IOException e) {
48             e.printStackTrace();
49         }
50     }
51 }
52
53 class ZerocopyClient {
54     public static void main(String[] args) throws IOException {
55         ZerocopyClient sfc = new ZerocopyClient();
56         sfc.testSendfile();
57     }
58
59     public void testSendfile() throws IOException {
60         String host = "localhost";
61         int port = 9026;
62         SocketAddress sad = new InetSocketAddress(host, port);
63         SocketChannel sc = SocketChannel.open();
64         sc.connect(sad);
65         sc.configureBlocking(true);
66
67         String fname = "src/main/java/zerocopy/test.data";
68         FileChannel fc = new FileInputStream(fname).getChannel();
69         long start = System.nanoTime();
70         long nsent = 0, curnset = 0;
71         curnset = fc.transferTo(0, fc.size(), sc);
72         System.out.println("发送的总字节数:" + curnset + " 耗时(ns):" + (System.nanoTime() - start));
73         try {
74             sc.close();
75             fc.close();
76         } catch (IOException e) {
77             System.out.println(e);
78         }
79     }
80 }

文件到文件的零拷贝:

 1 /**
 2  * disk-disk零拷贝
 3  */
 4 class ZerocopyFile {
 5     @SuppressWarnings("resource")
 6     public static void transferToDemo(String from, String to) throws IOException {
 7         FileChannel fromChannel = new RandomAccessFile(from, "rw").getChannel();
 8         FileChannel toChannel = new RandomAccessFile(to, "rw").getChannel();
 9
10         long position = 0;
11         long count = fromChannel.size();
12
13         fromChannel.transferTo(position, count, toChannel);
14
15         fromChannel.close();
16         toChannel.close();
17     }
18
19     @SuppressWarnings("resource")
20     public static void transferFromDemo(String from, String to) throws IOException {
21         FileChannel fromChannel = new FileInputStream(from).getChannel();
22         FileChannel toChannel = new FileOutputStream(to).getChannel();
23
24         long position = 0;
25         long count = fromChannel.size();
26
27         toChannel.transferFrom(fromChannel, position, count);
28
29         fromChannel.close();
30         toChannel.close();
31     }
32
33     public static void main(String[] args) throws IOException {
34         String from = "src/main/java/zerocopy/1.data";
35         String to = "src/main/java/zerocopy/2.data";
36         // transferToDemo(from,to);
37         transferFromDemo(from, to);
38     }
39 }

6、参考资料

https://my.oschina.net/cloudcoder/blog/299944

时间: 2024-10-15 10:32:18

Java零拷贝的相关文章

零拷贝Zero copy-linux and java

背景-几种拷贝方式 方式1:Copying in Two Sample System Calls read(file, tmp_buf, len); write(socket, tmp_buf, len); 首先,调用read时,文件A copy到了kernel模式: 之后,CPU控制将kernel模式数据copy到user模式下: 调用write时,先将user模式下的内容copy到kernel模式下的socket的buffer中: 最后将kernel模式下的socket buffer的数据c

java的零拷贝机制

转:https://blog.csdn.net/zhouhao88410234/article/details/77574689?fps=1&locationNum=9 为何要懂零拷贝原理?因为rocketmq存储核心使用的就是零拷贝原理. io读写的方式 中断 DMA 中断方式 中断方式的流程图如下: 用户进程发起数据读取请求 系统调度为该进程分配cpu cpu向io控制器(ide,scsi)发送io请求 用户进程等待io完成,让出cpu 系统调度cpu执行其他任务 数据写入至io控制器的缓冲

从rocketMQ到零拷贝

rocketMQ是阿里的开源MQ,号称很强大很强大 rocketMQ的网络实现是依赖netty http://my.oschina.net/plucury/blog/192577 这篇文章中描述了netty的零拷贝实现: 根据文中的描述,"Linux中的sendfile()以及Java NIO中的FileChannel.transferTo()方法都实现了零拷贝的功能",似乎"FileChannel.transferTo()和sendfile()是同一层的实现",n

理解Netty中的零拷贝(Zero-Copy)机制【转】

理解零拷贝 零拷贝是Netty的重要特性之一,而究竟什么是零拷贝呢? WIKI中对其有如下定义: "Zero-copy" describes computer operations in which the CPU does not perform the task of copying data from one memory area to another. 从WIKI的定义中,我们看到"零拷贝"是指计算机操作的过程中,CPU不需要为数据在内存之间的拷贝消耗资源

深入剖析Linux IO原理和几种零拷贝机制的实现

深入剖析Linux IO原理和几种零拷贝机制的实现 来源 https://zhuanlan.zhihu.com/p/83398714 零壹技术栈      公众号[零壹技术栈] 前言 零拷贝(Zero-copy)技术指在计算机执行操作时,CPU 不需要先将数据从一个内存区域复制到另一个内存区域,从而可以减少上下文切换以及 CPU 的拷贝时间.它的作用是在数据报从网络设备到用户程序空间传递的过程中,减少数据拷贝次数,减少系统调用,实现 CPU 的零参与,彻底消除 CPU 在这方面的负载.实现零拷贝

7. NIO与零拷贝

一.零拷贝 1.介绍 零拷贝描述的是CPU不执行拷贝数据从一个存储区域到另一个存储区域的任务,这通常用于通过网络传输一个文件时以减少CPU周期和内存带宽. 优点: 减少甚至完全避免不必要的CPU拷贝,从而让CPU解脱出来去执行其他的任务 减少内存带宽的占用 通常零拷贝技术还能够减少用户空间和操作系统内核空间之间的上下文切换 2.传统copy机制 Java 传统 IO 和 网络编程的一段代码: 1 File file = new File("test.txt"); 2 RandomAcc

[转]零拷贝的实现原理

"先从简单开始,实现下这个场景:从一个文件中读出数据并将数据传到另一台服务器上?" "为啥写这个?" "你先写" "行..." 1分钟后 "我写了伪代码" File.read(file, buf, len); Socket.send(socket, buf, len); "这里涉及到了几次数据拷贝?" "2次?磁盘拷贝到内存,内存拷贝到Socket?" "e

Linux 中的零拷贝技术,第 2 部分

技术实现 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.第一部分主要介绍了一些零拷贝技术的相关背景知识,简要概述了 Linux 为什么需要零拷贝技术以及 Linux 中都有哪几种零拷贝技术.本文是本系列文章的第二部分,针对第一部分内容中提到的几种零拷贝技术分别进行更详细的介绍,并对这些零拷贝技术的优缺点进行分析. 0 评论: 黄 晓晨, 软件工程师, IBM 冯 瑞, 软件工程师, IBM 2011 年 1

Linux 中的零拷贝技术,第 1 部分

概述 本系列由两篇文章组成,介绍了当前用于 Linux 操作系统上的几种零拷贝技术,简单描述了各种零拷贝技术的实现,以及它们的特点和适用场景.本文是本系列文章的第一部分,主要是介绍一些零拷贝技术的相关背景知识,简要概述了 Linux 为什么需要零拷贝技术以及 Linux 中都有哪几种零拷贝技术. 1 评论: 黄 晓晨, 软件工程师, IBM 冯 瑞, 软件工程师, IBM 2011 年 1 月 27 日 内容 在 IBM Bluemix 云平台上开发并部署您的下一个应用. 现在就开始免费试用 引