大家好,又见面了,我是你们的朋友全栈君。
Java 零拷贝
参考:
- Java中的零拷贝
零拷贝(英语: Zero-copy) 技术是指计算机执行操作时,CPU不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省CPU周期和内存带宽。
- 零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率
- 零拷贝技术减少了用户进程地址空间和内核地址空间之间因为上:下文切换而带来的开销
传统的IO数据读写 如下的例子,Java传统IO和网络编程的一段代码
代码语言:javascript复制File file = new File("test.txt");
RandomAccessFile raf = new RandomAccessFile(file, "rw");
byte[] arr = new byte[(int) file.length()];
raf.read(arr);
Socket socket = new ServerSocket(8090).accept();
socket.getOutputStream().write(arr);
实际上就是一个文件读和文件写的过程 传统IO读写示意图
1.DMA(Direct Memory Access,直接内存拷贝,即经过CPU的拷贝)等待数据准备好,把磁盘数据读取到操作系统内核缓冲区; 2.用户进程,将内核缓冲区的数据copy到用户空间。 3.读取文件,再用socket发送出去,再将用户空间的数据copy到socket网络发送缓冲区(属于操作系统内核的缓冲区); 4.将socket buffer的数据,copy到网卡,由网卡进行网络传输。
传统的IO进行了4次拷贝,进行了3次上下文切换。4次拷贝,其中两次是DMA copy,两次是CPU copy
mmap优化 mmap通过内存映射,将文件映射到内核缓冲区,同时,用户空间可以共享内核空间的数据。这样,在进行网络传输时,就可以减少内核空间到用户控件的拷贝次数
这种方式的I/O原理就是将用户缓冲区(user buffer)的内存地址和内核缓冲区(kernel buffer)的内存地址做一个映射,也就是说系统在用户态可以直接读取并操作内核空间的数据。
可见使用mmap进行IO,进行了3次拷贝,进行了3次上下文切换
Linux支持的零拷贝
1.sendfile linux 2.1支持的sendfile
当调用sendfile()时,DMA将磁盘数据复制到kernel buffer,然后将内核中的kernel buffer直接拷贝到socket buffer; 一旦数据全都拷贝到socket buffer,sendfile()系统调用将会return、代表数据转化的完成。 socket buffer里的数据就能在网络传输了。
sendfile
会经历:3次拷贝,1次CPU copy 2次DMA copy,以及2次上下文切换
提示 – 零拷贝是从操作系统角度来看的,是指没有CPU拷贝
Linux在2.4版本中,做了一些修改,避免了从内核缓冲区拷贝到Socket buffer的操作,直接拷贝到协议栈,从而减少了一次数据拷贝
会经历2次拷贝: 0次cpu copy,2次DMA copy
mmap和sendFile的区别 1.mmap适合小数据量读写,sendFile适合大文件传输 2.mmap需要4次上下文切换,3次数据拷贝;sendFile需要3次上下文切换,最少2次数据拷贝 3.sendFile可以利用DMA放肆,减少CPU拷贝,mmap则不能(必须从内核拷贝到Socket缓冲区)
传统方式
如下的服务端OldIOServer
public class OldIOServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(7001);
while (true) {
Socket socket = serverSocket.accept();
DataInputStream dataInputStream = new DataInputStream(socket.getInputStream());
try {
byte[] byteArray = new byte[4096];
while (true) {
int readCount = dataInputStream.read(byteArray, 0, byteArray.length);
if (readCount == -1) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
客户端OldIOClient
public class OldIOClient {
public static void main(String[] args) throws Exception{
Socket socket = new Socket("localhost", 7001);
String fileName = "test.txt";
InputStream inputStream = new FileInputStream(fileName);
DataOutputStream dataOutputStream = new DataOutputStream(socket.getOutputStream());
byte[] buffer = new byte[4096];
long readCount;
long total = 0;
long startTime = System.currentTimeMillis();
while ((readCount = inputStream.read(buffer)) >= 0) {
total = readCount;
dataOutputStream.write(buffer);
}
System.out.println("发送总字节数:" total ", 耗时:" (System.currentTimeMillis() - startTime));
dataOutputStream.close();
socket.close();
inputStream.close();
}
}
发送的test.txt
文件内容如下:
控制台输出如下:
NIO方式
服务端:
代码语言:javascript复制public class NewIOServer {
public static void main(String[] args) throws Exception{
InetSocketAddress address = new InetSocketAddress(7001);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
ServerSocket serverSocket = serverSocketChannel.socket();
serverSocket.bind(address);
ByteBuffer byteBuffer = ByteBuffer.allocate(4096);
while (true) {
SocketChannel socketChannel = serverSocketChannel.accept();
int readCount = 0;
while (readCount != -1) {
try {
readCount = socketChannel.read(byteBuffer);
} catch (Exception ex) {
ex.printStackTrace();
}
//将buffer倒带
byteBuffer.rewind();
}
}
}
}
客户端:
代码语言:javascript复制public class NewIOClient {
public static void main(String[] args) throws Exception {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 7001));
String fileName = "test.txt";
//得到一个文件channel
FileChannel fileChannel = new FileInputStream(fileName).getChannel();
//准备发送
long startTime = System.currentTimeMillis();
//transferTo底层使用零拷贝
/** * This method is potentially much more efficient than a simple loop that reads from this channel and writes to the target channel. * Many operating systems can transfer bytes directly from the filesystem cache to the target channel without actually copying them */
long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);
System.out.println("发送总字节数:" transferCount ", 耗时:" (System.currentTimeMillis() - startTime));
fileChannel.close();
}
}
控制台输出如下,因为我这里传输的文件比较小,所以没什么效果:
BIO、NIO、AIO的比较
BIO | NIO | AIO | |
---|---|---|---|
IO模型 | 同步阻塞 | 同步非阻塞(多路复用) | 异步非阻塞 |
编程难度 | 简单 | 复杂 | 复杂 |
可靠性 | 差 | 好 | 好 |
吞吐量 | 低 | 高 | 高 |
1.同步阻塞:到理发店理发,一直等待理发师,直到轮到自己 2.同步非阻塞:到理发店理发,发现前面有其它人理发,给理发师说下,先干其它的事情,一会儿过来看是否轮到自己 3.异步非阻塞:给理发师打电话,让理发师上门服务,自己干其它事情,理发师自己来给你理发
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/191158.html原文链接:https://javaforall.cn