JAVA Zero Copy的相關知識

介紹

     java 的zero copy多在網絡應用程序中使用。Java的libaries在linux和unix中支持zero copy,關鍵的api是java.nio.channel.FileChannel的transferTo(),transferFrom()方法。咱們能夠用這兩個方法來把bytes直接從調用它的channel傳輸到另外一個writable byte channel,中間不會使data通過應用程序,以便提升數據轉移的效率。 java

    許多web應用都會向用戶提供大量的靜態內容,這意味着有不少data從硬盤讀出以後,會原封不動的經過socket傳輸給用戶。這種操做看起來可能不會怎麼消耗CPU,可是實際上它是低效的:kernal把數據從disk讀出來,而後把它傳輸給user級的application,而後application再次把一樣的內容再傳回給處於kernal級的socket。這種場景下,application實際上只是做爲一種低效的中間介質,用來把disk file的data傳給socket。 linux

    data每次穿過user-kernel boundary,都會被copy,這會消耗cpu,而且佔用RAM的帶寬。幸運的是,你能夠用一種叫作Zero-Copy的技術來去掉這些無謂的 copy。應用程序用zero copy來請求kernel直接把disk的data傳輸給socket,而不是經過應用程序傳輸。Zero copy大大提升了應用程序的性能,而且減小了kernel和user模式的上下文切換 web

    

    使用kernel buffer作中介(而不是直接把data傳到user buffer中)看起來比較低效(多了一次copy)。然而實際上kernel buffer是用來提升性能的。在進行讀操做的時候,kernel buffer起到了預讀cache的做用。當寫請求的data size比kernel buffer的size小的時候,這可以顯著的提高性能。在進行寫操做時,kernel buffer的存在可使得寫請求徹底異步。 centos

      悲劇的是,當請求的data size遠大於kernel buffer size的時候,這個方法自己變成了性能的瓶頸。由於data須要在disk,kernel buffer,user buffer之間拷貝不少次(每次寫滿整個buffer)。 api

    而Zero copy正是經過消除這些多餘的data copy來提高性能。 網絡

傳統的數據複製方式及涉及到的上下文切換:

      經過網絡把一個文件傳輸給另外一個程序,在OS的內部,這個copy操做要經歷四次user mode和kernel mode之間的上下文切換,甚至連數據都被拷貝了四次,以下圖: app

    具體步驟以下: dom

  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是獨立並且異步的。

        Traditional data copying approach

        Traditional context switches


數據轉移(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的拷貝操做。

        Data copies when transferTo() and gather operations are used

            Context switching when using transferTo()


代碼樣例:

展現經過網絡把一個文件從client傳到server的過程 socket

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();
		}
	}
}
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的用法 

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

相關文章
相關標籤/搜索