JAVA 零拷贝技术和主流中间件零拷贝技术应用

目录

    • 介绍
      • Java代码里面有哪些零拷贝技术
      • java 中`文件读写`方式主要分为
      • 什么是`FileChannel`
        • mmap实现
        • sendfile实现
    • 文件IO实战
      • 需求
      • 代码编写实战
        • IOTest.java 文件
        • 上传阿里云,测试运行代码看耗时
          • 为啥带buffer的IO比普通IO性能高?
          • BufferedInputStream为啥性能高点
    • 性能差异分析
      • 原理分析
    • 中间件零拷贝的应用
      • Nginx 使用就是 sendfile 零拷贝
      • RocketMQ
      • 其它中间件
      • 优缺点

介绍

Java代码里面有哪些零拷贝技术

  • Java NIO对mmap -> fileChannel.map()
  • Java NIO对sendfile -> fileChannel.transferTo()fileChannel.transferFrom()
  • API是否使用零拷贝依赖于底层的系统实现

java 中文件读写方式主要分为

  • IO输入输出流,存在于 java.io 中【普通】
 public static void inputStream(String inputFilePathStr, String outputFilePathStr) {
        long start = System.currentTimeMillis();
        try (InputStream fis = new FileInputStream(inputFilePathStr);
             FileOutputStream fos = new FileOutputStream(outputFilePathStr);
        ) {
            byte[] buf = new byte[1024];
            int len = 0;
            while ((len = fis.read(buf)) != -1) {
                fos.write(buf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println(end - start);
    }
  • FileChannel 文件通道 ,存在于java.nio.channels.FileChannel 中 【高级】

什么是FileChannel

  • 是一个连接到文件的通道,可以通过文件通道读写文件,该常被用于高效的网络/文件的数据传输和大文件拷贝
  • 应用程序使用FileChannel 写完以后,数据是在PageCache上的,操作系统不定时的把PageCache的数据写入到磁盘
    • 使用 channel.force(true) 把文件相关的数据强制刷入磁盘上去,避免宕机数据丢失
  • 使用之前必须先打开它,但是无法直接 new 一个 FileChannel
  • 常规通过使用一个InputStream、OutputStreamRandomAccessFile来获取一个FileChannel实例
RandomAccessFile randomAccessFile = new RandomAccessFile("/usr/data/xdclass_nio-data.txt", "rw");
FileChannel inChannel = randomAccessFile.getChannel();
mmap实现
- map方法,把文件映射成内存映射文件
- `MappedByteBuffer`,是抽象类 也是ByteBuffer的子类  ,具体实现子类是DirectByteBuffer,可被通道进行读写
- 一次 map 大小要限制 2G 内,过大 map 会增加虚拟内存回收和重新分配的压力 ,直接报错
	- `FileChannel.java` 中的 `map` 对 `long size` 进行了限制,不能大于 `Integer.MAX_VALUE`,否则就报错
	- JDK 层的为何要限制,是因为底层 C++的类型,无符号int类型最大是2^31 -1, 2^31 -1 字节就是 2GB - 1B

在这里插入图片描述

//position: 文件开始
//size:映射的文件区域大小
//mode: 访问该内存映射文件的方式: READ_ONLY(只读) READ_WRITE(读写),PRIVATE(创建一个修改副本)
MappedByteBuffer map(int mode,long position,long size); 
sendfile实现
  • 将字节从此通道的文件传输到给定的可写入字节通道
  • 返回值为真实拷贝的size,最大拷贝2G,超出2G的部分将丢弃
//position - 文件中的位置,从此位置开始传输,必须非负数
//count - 要传输的最大字节数,必须非负数
//target - 目标通道
//返回:实际已传输的字节数,可能为零
fileChannel.transferTo(long position, long count, WritableByteChannel target)
  • 将字节从给定的可读取字节通道传输到此通道的文件中
  • 对比 从源通道读取并将内容写入此通道的循环语句相比,此方法更高效
//src - 源通道
//position - 文件中的位置,从此位置开始传输,必须非负数
//count - 要传输的最大字节数, 必须非负数
//返回:实际已传输的字节数,可能为零
fileChannel.transferFrom(ReadableByteChannel src, long position, long count)
  • 注意
    • 上述方法允许将一个通道连接到另一个通道,不需要在用户态和内核态来回复制,同时通道间的内核态数据也无需复制
    • transferTo()只有源为FileChannel才支持transfer这种高效的复制方式,其他如SocketChannel都不支持transfer模式
    • 一般可以做FileChannel->FileChannel 和 FileChannel->SocketChannel的transfer零拷贝
      在这里插入图片描述
      在这里插入图片描述

文件IO实战

需求

  • 实现一个文件拷贝,对比不同IO方式性能差异,文件大小 200MB~5GB
  • 编码类型
    • 普通java的io
    • 普通java的带bufferio
    • 零拷贝实现之mmapio
    • 零拷贝实现之sendfileio
  • 运行环境
    • 阿里云Linux CentOS7.X
    • 安装JDK11 配置全局环境变量
      • 配置 vim /etc/profile
      • 环境变量立刻生效
        • source /etc/profile
        • 查看安装情况 java -version
JAVA_HOME=/usr/local/software/jdk11
CLASSPATH=$JAVA_HOME/lib/
PATH=$PATH:$JAVA_HOME/bin
export PATH JAVA_HOME CLASSPATH

代码编写实战

IOTest.java 文件
package net.demo;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;

/**
 *  java IOTest.java "io" "source" "target"
 */
public class IOTest {
    public static void main(String[] args) {
        String type = args[0];
        String inputFilePath = args[1];
        String outputFilePath = args[2];
        if ("io".equalsIgnoreCase(type)) {
            inputStreamCopyFile(inputFilePath, outputFilePath);

        } else if ("buffer".equalsIgnoreCase(type)) {
            bufferInputStreamCopyFile(inputFilePath, outputFilePath);

        } else if ("mmap".equalsIgnoreCase(type)) {
            mmapCopyFile(inputFilePath, outputFilePath);

        } else if ("sendfile".equalsIgnoreCase(type)) {
            sendfileCopyFile(inputFilePath, outputFilePath);

        }
    }

    private static void sendfileCopyFile(String inputFilePath, String outputFilePath) {
        long start = System.currentTimeMillis();

        try (
                FileChannel channelIn = new FileInputStream(inputFilePath).getChannel();
                FileChannel channelOut  = new FileOutputStream(outputFilePath).getChannel();
        ) {
            // 代码一:针对小于2GB的问题,返回值为真实拷贝的size,最大拷贝2G,超出2G的部分将丢弃,最终拷贝文件大小只有2GB多点
            // channelIn.transferTo(0, channelIn.size(), channelOut);

            //代码二:针对大于2GB的文件,方案
            //获取文件总大小
            long size = channelIn.size();

            for (long left = size; left > 0; ) {
                //transferSize所拷贝过去的真实长度,size - left计算出下次要拷贝的位置
                long transferSize = channelIn.transferTo((size - left), left, channelOut);
                System.out.println("总大小:"+size+",拷贝大小:"+transferSize);
                //left剩余字节多少
                left = left - transferSize;
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end - start));
    }

    private static void mmapCopyFile(String inputFilePath, String outputFilePath) {
        long start = System.currentTimeMillis();

        try (
                FileChannel channelIn = new FileInputStream(inputFilePath).getChannel();
                FileChannel channelOut = new RandomAccessFile(outputFilePath, "rw").getChannel();

        ) {
            long size = channelIn.size();
            System.out.println("mappedFile:" + size);
            MappedByteBuffer mbbi = channelIn.map(FileChannel.MapMode.READ_ONLY, 0, size);
            MappedByteBuffer mbbo = channelOut.map(FileChannel.MapMode.READ_WRITE, 0, size);
            for (int i = 0; i < size; i++) {
                byte b = mbbi.get(i);
                mbbo.put(i, b);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end - start));
    }

    private static void bufferInputStreamCopyFile(String inputFilePath, String outputFilePath) {
        long start = System.currentTimeMillis();
        try (
                BufferedInputStream bis = new BufferedInputStream( new FileInputStream(inputFilePath));
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outputFilePath));
        ) {

//            byte[] buf = new byte[64];
            byte[] buf = new byte[1];//方便测试字节改用1
            int len;
            while ((len = bis.read(buf)) != -1) {
                bos.write(buf);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end - start));
    }

    private static void inputStreamCopyFile(String inputFilePath, String outputFilePath) {

        long start = System.currentTimeMillis();
        try (
                FileInputStream fis = new FileInputStream(inputFilePath);
                FileOutputStream fos = new FileOutputStream(outputFilePath)
        ) {

//            byte[] buf = new byte[64];
            byte[] buf = new byte[1];//方便测试字节改用1
            int len;
            while ((len = fis.read(buf)) != -1) {
                fos.write(buf);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        long end = System.currentTimeMillis();
        System.out.println("耗时:"+(end - start));

    }
}
上传阿里云,测试运行代码看耗时
#释放所有缓存
echo 3 > /proc/sys/vm/drop_caches

#查看内存使用
free -h -w -s 1

#查看机器负载
top 

java IOTest.java "io" "/usr/local/software/slow.log" "/usr/local/software/iotest/slow.log"
java IOTest.java "buffer" "/usr/local/software/slow.log" "/usr/local/software/iotest/slow.log"
java IOTest.java "mmap" "/usr/local/software/slow.log" "/usr/local/software/iotest/slow.log"
java IOTest.java "sendfile" "/usr/local/software/slow.log" "/usr/local/software/iotest/slow.log"
  • 局部性原理:指计算机在执行某个程序时,倾向于使用最近使用的数据
    • 时间局部性:如果程序中的某条指令一旦被执行,则不久的将来该指令可能再次被执行
    • 空间局部性:一旦程序访问了某个存储单元,在不久的将来,其附近的存储单元也最有可能被访问
为啥带buffer的IO比普通IO性能高?
  • 文件读取,OS的做了什么优化操作
    • 每次读数据的时候,系统根据局部性原理,通过 DMA 会读入更多的数据到内核缓冲区里面
    • OS根据局部性原理会在一次 read()系统调用过程中预读更多的文件数据缓存在内核IO缓冲区中
    • 当继续访问的文件数据在缓冲区中时便直接拷贝数据到进程缓冲区,避免了再次的低效率磁盘IO操作
    • OS已经帮减少磁盘IO操作次数,提高了性能
BufferedInputStream为啥性能高点
  • 通过减少系统调用次数来提高性能了IO性能,即减少CPU在内核态和用户态的上下文切换次数
  • 在 kernel buffer 把数据拷贝到 user buffer 的时候,把数据多拷贝到 user buffer 中
  • 比如
    • 进程user buffer想要向内核态读取4个字节,但是内核态上面有8个字节数据,大方点都拷贝到user buffer里面
    • 当进程user buffer下次要再读取4个字节的时候,因为数据已经在user buffer中了,就不需要上下文切换

性能差异分析

  • 普通拷贝
    • 普通java的io流【慢】1800秒
    • 普通java的带buffer的io【快】80秒
  • 零拷贝(1~2g文件差别不大)
    • 零拷贝实现之mmap的io【快】30秒
    • 零拷贝实现之sendfile的io【快】30秒

原理分析

  • mmap
    在这里插入图片描述

  • sendfile
    在这里插入图片描述

中间件零拷贝的应用

Nginx 使用就是 sendfile 零拷贝

  • Web Server 处理静态页面请求时,是从磁盘中读取网页的内容,所以选择这个
    • 因为 sendfile不能在应用程序中修改数据,所以适合 静态文件服务器或者是直接转发数据的代理服务器
      在这里插入图片描述

RocketMQ

  • 主要是mmap,也有小部分使用sendfile
  • rocketMQ在消息存盘和网络发送使用mmap, 单个CommitLog文件大小默认1GB
    • 要在用户进程内处理数据,然后再发送出去的话,用户空间和内核空间的数据传输就是不可避免的
      在这里插入图片描述

其它中间件

  • Kafka :主要是sendfile,也有小部分使用mmap
    • kafka 在客户端和 broker 进行数据传输时,broker 使用 sendfile 系统调用,类似 【FileChannel.transferTo】 API,将磁盘文件读到 OS 内核缓冲区后,直接转到 socket buffer 进行网络发送,即 Linux 的 sendfile
  • Hadoop、Tomcat、Kafka、Netty、Zookeeper、Rabbitmq… 等都有用到零拷贝

优缺点

  • 零拷贝的目标

    • 解放CPU,避免CPU做太多事情
    • 减少内存带宽占用
    • 减少用户态和内核态上下文切换过多
    • 在文件较小的时候 mmap 耗时更短,当文件较大时 sendfile 的方式最优
  • 零拷贝方式对比

    • sendfile
      • 无法在调用过程中修改数据,只适用于应用程序不需要对所访问数据进行处理修改情况
      • 场景
        • 比如 静态文件传输,MQ的Broker发送消息给消费者
        • 如果想要在传输过程中修改数据,可以使用mmap系统调用
      • 文件大小:适合大文件传输
      • 切换和拷贝:2次上下文切换,最少 2 次数据拷贝
    • mmap
      • 在mmap调用可以在应用程序中直接修改Page Cache中的数据,使用的是mmap+write两步
        • 调用比sendfile成本高,但优于传统I/O的拷贝实现方式,虽然比 sendfile 多了上下文切换
        • 但用户空间与内核空间并不需要数据拷贝,在正确使用情况下并不比 sendfile 效率差
      • 场景
        • 多个线程以只读的方式同时访问一个文件, mmap 机制下多线程共享同一物理内存空间,节约内存
      • 文件大小:适合小数据量读写
      • 切换和拷贝:4 次上下文切换,3 次数据拷贝

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/939273.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

系统移植——Linux 内核顶层 Makefile 详解

一、概述 Linux Kernel网上下载的版本很多NXP等有自己对应的版本。需要从网上直接下载就可以。 二、Linux内核初次编译 编译内核之前需要先在 ubuntu 上安装 lzop 库 sudo apt-get install lzop 在 Ubuntu 中 新 建 名 为 “ alientek_linux ” 的 文 件夹 &#xff0c; …

Reactor

文章目录 正确的理解发送double free问题解决 1.把我们的reactor进行拆分2.链接管理3.Reactor的理论 listensock只需要设置_recv_cb&#xff0c;而其他sock&#xff0c;读&#xff0c;写&#xff0c;异常 所以今天写nullptr其实就不太对&#xff0c;添加为空就没办法去响应事件…

【深度学习】 零基础介绍卷积神经网络(CNN)

CNN学习 零基础介绍写个CNN最简单的代码一. 概述二. 搭建CNN1. 输入层2. 卷积层3. 激活层4. 池化层5. 全连接层6. 网络搭建小结7. 损失函数8. 梯度下降9. 反向传播10. 模型评估与正则化11. 尝试搭建自己的第一个CNN 三. 经典CNN结构四. 猫狗识别项目实践1. Paddle实现版本&…

Leetcode打卡:找到稳定山的下标

执行结果&#xff1a;通过 题目&#xff1a; 3258 找到稳定山的下标 有 n 座山排成一列&#xff0c;每座山都有一个高度。给你一个整数数组 height &#xff0c;其中 height[i] 表示第 i 座山的高度&#xff0c;再给你一个整数 threshold 。 对于下标不为 0 的一座山&#xf…

leetcode刷题日记03——javascript

题目3&#xff1a; 回文数https://leetcode.cn/problems/palindrome-number/ 给你一个整数 x &#xff0c;如果 x 是一个回文整数&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 回文数是指正序&#xff08;从左向右&#xff09;和倒序&#xff08;从右向…

服务器数据恢复—RAIDZ离线硬盘数超过热备盘数导致阵列崩溃的数据恢复案例

服务器存储数据恢复环境&#xff1a; ZFS Storage 7320存储阵列中有32块硬盘。32块硬盘分为4组&#xff0c;每组8块硬盘&#xff0c;共组建了3组RAIDZ&#xff0c;每组raid都配置了热备盘。 服务器存储故障&#xff1a; 服务器存储运行过程中突然崩溃&#xff0c;排除人为误操…

Tact智能合约安全实践:TON生态系统中的常见错误

TON&#xff08;The Open Network&#xff09;以其创新特性和强大的智能合约性能&#xff0c;不断拓宽区块链技术的边界。基于早期的区块链平台&#xff08;如以太坊等&#xff09;的经验与教训&#xff0c;TON为开发者提供了一个更加高效且灵活的开发环境。其中推动这一进步的…

C进阶—指针(1)

若是阁下满意的话&#xff0c;可否一键三连呢&#xff01; 第一篇进阶指针就是先了解各种新的概念&#xff08;用法我们后面几篇再详细说&#xff01;先只介绍概念&#xff09;&#xff0c;有疑惑很正常&#xff0c;只是暂时的&#xff0c;我们一起来看看吧&#xff01; 字符指…

【Python使用】嘿马头条项目从到完整开发教程第9篇:缓存,1 缓存穿透【附代码文档】

本教程的知识点为:简介 1. 内容 2. 目标 产品效果 ToutiaoWeb虚拟机使用说明 数据库 理解ORM 作用 思考&#xff1a; 使用ORM的方式选择 数据库 SQLAlchemy操作 1 新增 2 查询 all() 数据库 分布式ID 1 方案选择 2 头条 使用雪花算法 &#xff08;代码 toutiao-backend/common/…

谷歌浏览器的扩展程序自动更新设置

谷歌浏览器是全球最受欢迎的网络浏览器之一&#xff0c;其扩展程序更是为用户提供了丰富的功能。然而&#xff0c;随着时间的推移&#xff0c;扩展程序需要更新以修复漏洞、提升性能或增加新功能。本文将详细介绍如何在Chrome中设置扩展程序的自动更新。&#xff08;本文由http…

LabVIEW与PLC点位控制及OPC通讯

在工业自动化中&#xff0c;PLC通过标准协议&#xff08;如Modbus、Ethernet/IP等&#xff09;与OPC Server进行数据交换&#xff0c;LabVIEW作为上位机通过OPC客户端读取PLC的数据并进行监控、控制与处理。通过这种方式&#xff0c;LabVIEW能够实现与PLC的实时通信&#xff0c…

在Windows Server路由和远程访问服务中启用L2TP/IPsec VPN

背景 路由和远程访问服务&#xff08;Routing and Remote Access Services&#xff0c;RRAS&#xff09;是Windows Server上的一个角色&#xff0c;包含很多功能&#xff0c;可以用来搭建VPN。然而&#xff0c;在什么也不做的初始配置中&#xff0c;它只允许PPTP协议连接。然而…

Android简洁缩放Matrix实现图像马赛克,Kotlin

Android简洁缩放Matrix实现图像马赛克&#xff0c;Kotlin 原理&#xff0c;通过Matrix把一个原图缩小到原先的1/n&#xff0c;然后再把缩小后的小图放大n倍&#xff0c;自然就是马赛克效果&#xff08;相当于是放大后像素“糊”成一片了&#xff09;。 import android.content.…

《Posterior Collapse and Latent Variable Non-identifiability》

看起来像一篇很有用的paper&#xff0c;而且还是23年的 没看完 后边看不懂了 Abstract 现有的解释通常将后验崩塌归因于由于变分近似而使用神经网络或优化问题。 而本文认为后验崩塌是潜在变量不可识别性的问题(a problem of latent variable non-identifiability) 本文证明了…

网络视频监控平台/安防监控/视频综合管理Liveweb视频汇聚平台解决方案

一、当前现状分析 当前视频资源面临以下问题&#xff1a; 1&#xff09;不同单位在视频平台建设中以所属领域为单位&#xff0c;设备品牌众多&#xff0c;存在的标准不一&#xff0c;各系统之间也没有统一标准&#xff1b; 2&#xff09;各单位视频平台建设分散、统筹性差&am…

【前端爬虫】关于如何获取自己的请求头信息(user-agent和cookie)

注意&#xff1a;由于user-agent和cookie中保存了部分账户信息&#xff0c;所以一定不要随意泄露给他人&#xff01;&#xff01;&#xff01; 1.首先打开某个页面&#xff0c;点击键盘的F12键进入控制台&#xff0c;或者鼠标右键页面选择打开控制台 2.然后点击控制台上方的网…

共创共建!葡萄城 SpreadJS 完成 HarmonyOS NEXT 操作系统兼容认证

最新技术资源&#xff08;建议收藏&#xff09; https://www.grapecity.com.cn/resources/ 近日&#xff0c;华为“企业工作必备应用鸿蒙化论坛”在北京圆满落幕&#xff0c;论坛汇聚了众多行业精英和合作伙伴&#xff0c;聚焦讨论企业数字化转型与原生鸿蒙生态融合等话题。葡萄…

单项链表的学习

1:链表概念 链表是⼀种物理存储结构上⾮连续、⾮顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的。 1&#xff1a;结点 与顺序表不同的是&#xff0c;链表⾥的每节"⻋厢"都是独⽴申请下来的空间&#xff0c;我们称之为“结点 / 结…

基于大语言模型的多代理下一代制造系统能灵活动态管理制造资源的高效调度方法

摘要 论文地址&#xff1a;https://arxiv.org/pdf/2405.16887 随着生产率的提高&#xff0c;客户对多品种、小批量生产的需求也在不断增加&#xff0c;这反过来又对制造系统提出了更高的要求。由于这种需求&#xff0c;当生产任务频繁变化时&#xff0c;传统的制造系统往往无法…

FPGA-PS端编程1:

目标 在小梅哥的zynq 7015上&#xff0c;完成以下目标&#xff1a; 读取 S1 按键的电平&#xff0c; 当 S1 按键为按下状态时&#xff0c;驱动 PS LED 以 1S 的频率闪烁(注意理解 1S 的频率闪烁和 1S的时间翻转两种描述之间的差别)&#xff0c; 当 S1 释放后&#xff0c;停止…