JAVA Zero Copy

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


介绍

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

传统的数据复制方式及涉及到的上下文切换:

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

    具体步骤如下:

  1. read() 调用导致一次从user mode到kernel mode的上下文切换。在内部调用了sys_read() 来从文件中读取data。第一次copy由DMA (direct memory access)egine完成,将文件内容从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是独立而且异步的。

        

        


数据转移(data transfer): zero copy方式及涉及的上下文转换

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

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


            


代码样例:

展示通过网络把一个文件从client传到server的过程

?
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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
package zerocopy;
 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
 
public class TransferToServer {
     ServerSocketChannel listener = null ;
 
     protected void mySetup() {
         InetSocketAddress listenAddr = new InetSocketAddress( 9026 );
 
         try {
             listener = ServerSocketChannel.open();
             ServerSocket ss = listener.socket();
             ss.setReuseAddress( true );
             ss.bind(listenAddr);
             System.out.println( "监听的端口:" + listenAddr.toString());
         } catch (IOException e) {
             System.out.println( "端口绑定失败 : "
                     + listenAddr.toString() + " 端口可能已经被使用,出错原因: "
                     + e.getMessage());
             e.printStackTrace();
         }
 
     }
 
     public static void main(String[] args) {
         TransferToServer dns = new TransferToServer();
         dns.mySetup();
         dns.readData();
     }
 
     private void readData() {
         ByteBuffer dst = ByteBuffer.allocate( 4096 );
         try {
             while ( true ) {
                 SocketChannel conn = listener.accept();
                 System.out.println( "创建的连接: " + conn);
                 conn.configureBlocking( true );
                 int nread = 0 ;
                 while (nread != - 1 ) {
                     try {
                         nread = conn.read(dst);
                     } catch (IOException e) {
                         e.printStackTrace();
                         nread = - 1 ;
                     }
                     dst.rewind();
                 }
             }
         } 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
38
39
40
package zerocopy;
 
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
 
public class TransferToClient {
 
     public static void main(String[] args) throws IOException {
         TransferToClient sfc = new TransferToClient();
         sfc.testSendfile();
     }
 
     public void testSendfile() throws IOException {
         String host = "localhost" ;
         int port = 9026 ;
         SocketAddress sad = new InetSocketAddress(host, port);
         SocketChannel sc = SocketChannel.open();
         sc.connect(sad);
         sc.configureBlocking( true );
 
         String fname = "src/main/java/zerocopy/test.data" ;
         FileChannel fc = new FileInputStream(fname).getChannel();
         long start = System.nanoTime();
         long nsent = 0 , curnset = 0 ;
         curnset = fc.transferTo( 0 , fc.size(), sc);
         System.out.println( "发送的总字节数:" + curnset
                 + " 耗时(ns):"
                 + (System.nanoTime() - start));
         try {
             sc.close();
             fc.close();
         } catch (IOException e) {
             System.out.println(e);
         }
     }
}

其它zero copy的用法 

?
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
38
39
40
41
42
43
44
45
package zerocopy;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
 
public class ZerocopyDemo {
     @SuppressWarnings ( "resource" )
     public static void transferToDemo(String from, String to) throws IOException {
         FileChannel fromChannel = new RandomAccessFile(from, "rw" ).getChannel();
         FileChannel toChannel = new RandomAccessFile(to, "rw" ).getChannel();
 
         long position = 0 ;
         long count = fromChannel.size();
 
         fromChannel.transferTo(position, count, toChannel);
         
         fromChannel.close();
         toChannel.close();
     }
 
     @SuppressWarnings ( "resource" )
     public static void transferFromDemo(String from, String to)
             throws IOException {
         FileChannel fromChannel = new FileInputStream(from).getChannel();
         FileChannel toChannel = new FileOutputStream(to).getChannel();
 
         long position = 0 ;
         long count = fromChannel.size();
 
         toChannel.transferFrom(fromChannel, position, count);
         
         fromChannel.close();
         toChannel.close();
     }
     
     public static void main(String[] args) throws IOException {
         String from= "src/main/java/zerocopy/1.data" ;
         String to= "src/main/java/zerocopy/2.data" ;
//      transferToDemo(from,to);
         transferFromDemo(from,to);
     }
}

参考

https://www.ibm.com/developerworks/linux/library/j-zerocopy/

http://blog.csdn.net/flyingqr/article/details/6942645


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值