当前位置: 首页 > article >正文

Netty——零拷贝

文章目录

  • 1. 什么是零拷贝?
  • 2. 为什么需要零拷贝?
    • 2.1 传统 I/O 的拷贝流程
    • 2.2 零拷贝的优化
      • 2.2.1 通过 sendfile 系统调用
      • 2.2.2 通过 mmap (内存映射) 系统调用
  • 3. Netty 实现零拷贝的方式
    • 3.1 文件传输优化:FileRegion 封装
    • 3.2 直接内存 (Direct Memory)
    • 3.3 内存映射文件:MappedByteBuffer
    • 3.4 复合缓冲区:CompositeByteBuf
    • 3.5 切片与包装操作
  • 4. 零拷贝的优点
  • 5. 总结


1. 什么是零拷贝?

零拷贝 (Zero-Copy) 是一种 通过减少或避免数据在内存之间不必要的拷贝,从而提升系统性能的技术。它主要用于 操作系统内核用户程序 之间的数据传输场景。

2. 为什么需要零拷贝?

2.1 传统 I/O 的拷贝流程

以文件传输为例,传统 I/O 的拷贝流程如下所示:

  1. 从 磁盘 读取数据到 内核缓冲区:数据从磁盘通过 DMA(直接内存访问)传输到内核空间的缓冲区。
    从 磁盘 读取数据到 内核缓冲区
  2. 将数据从 内核缓冲区 拷贝到 用户空间:用户程序通过系统调用(如 read())将内核缓冲区的数据拷贝到用户空间的缓冲区。
    将数据从 内核缓冲区 拷贝到 用户空间
  3. 将数据从 用户空间 拷贝回 内核 Socket 缓冲区:用户程序通过系统调用(如 write())将数据从用户空间缓冲区拷贝到内核空间的 Socket 缓冲区。
    将数据从 用户空间 拷贝回 内核 Socket 缓冲区
  4. 将数据从 内核 Socket 缓冲区 发送到 网络:数据通过 DMA 传输到网卡,完成发送。
    将数据从 内核 Socket 缓冲区 发送到 网络

问题:传统流程需要 4 次拷贝2 次上下文切换(用户态 和 内核态 的相互切换),性能开销大。

2.2 零拷贝的优化

零拷贝主要通过 减少用户空间与内核空间的交互 的方式减少拷贝次数,具体可以使用的特性如下:

2.2.1 通过 sendfile 系统调用

通过 sendfile,直接在内核空间将文件数据传输到 Socket 缓冲区,避免了 2 次上下文切换和 1 次拷贝。步骤如下:

磁盘->内核缓冲区
文件缓冲区->Socket 缓冲区
内核->网络

注:第二步文件从内核的 文件缓冲区 拷贝到 Socket 缓冲区 使用的是 DMA 还是 CPU 拷贝需要看硬件是否支持 SG-DMA(分散聚合直接内存访问)。

  • 若硬件(如网卡)支持 SG-DMA,数据从 文件缓冲区 到 Socket 缓冲区 的传输由 DMA 完成,无需 CPU 参与拷贝。此时,内核只需告知 DMA 控制器数据位置和长度,DMA 直接在内核空间内搬运数据,完全释放 CPU 资源,实现更高效的零拷贝
  • 若硬件不支持 SG-DMA,内核会通过 CPU 拷贝将数据从 文件缓冲区 复制到 Socket 缓冲区。

2.2.2 通过 mmap (内存映射) 系统调用

mmap 系统调用会将文件映射到进程的虚拟地址空间,使得进程可以直接访问文件内容,而无需将文件内容拷贝到用户空间的缓冲区。过程如下:

  1. 映射文件到虚拟地址空间:应用程序调用 mmap,操作系统会创建一个虚拟内存区域,并将其与文件的物理地址进行映射。此时,文件内容并没有被实际加载到内存中,只是建立了映射关系。
    映射文件到虚拟地址空间
  2. 直接访问映射区域:应用程序可以像访问普通内存一样直接访问映射区域。当应用程序访问映射区域中的某个地址时,如果该地址对应的页面尚未加载到内存中,会触发 缺页中断。操作系统会根据映射关系,将文件中的相应数据从 磁盘 读取到 内核空间的文件缓冲区,并将该页面映射到进程的虚拟地址空间。
    直接访问映射区域
  3. 将数据从 文件缓冲区 发送到到 Socket 缓冲区:当需要将文件内容发送到网络时,应用程序可以直接操作映射区域。操作系统会将内核空间的 文件缓冲区 中的数据直接拷贝到 Socket 缓冲区
    将数据从 文件缓冲区 发送到到 Socket 缓冲区
  4. 将数据从 Socket 缓冲区 发送到 网络:将数据从 Socket 缓冲区 发送到 网卡
    将数据从 Socket 缓冲区 发送到 网络

在这个过程中,数据不需要经过用户空间的缓冲区,避免了 1 次数据拷贝

注意:看似 用户空间 中好像有也一块内存,与 文件缓冲区 一样,但其实 用户空间 用 页表 来映射 文件缓冲区 中内存。

3. Netty 实现零拷贝的方式

3.1 文件传输优化:FileRegion 封装

Netty 通过 FileRegion 类封装了 NIO 的 FileChannel 类的 transferTo() 方法,避免数据 在 内核空间 与 用户空间 的拷贝上下文切换。在 Linux 操作系统中,transferTo() 底层使用 sendfile 的系统调用实现 零拷贝

3.2 直接内存 (Direct Memory)

Netty 使用 直接内存 (Direct Memory) 来减少数据在 用户空间 和 内核空间 之间的拷贝。直接内存由操作系统直接管理,避免了在 用户空间 的中转。

  • 对于文件操作,使用 mmap 进行优化。
  • 对于网络传输,使用 sendfile 进行优化。

从而避免将内核空间中 文件缓冲区 的数据拷贝到 用户空间 和 2 次上下文切换。

3.3 内存映射文件:MappedByteBuffer

通过 FileChannel.map() 将文件映射到虚拟内存,直接通过内存地址操作文件数据。底层使用了 mmap 技术,避免了 内核空间 与 用户空间 之间的 数据拷贝 和 线程切换。

3.4 复合缓冲区:CompositeByteBuf

CompositeByteBuf 将多个 ByteBuf 组合成一个逻辑上的整体,无需物理合并数据,减少内存复制

3.5 切片与包装操作

通过 ByteBuf 对象的 slice()duplicate()Unpooled.wrappedBuffer() 共享数据,而非复制数据,使用这些方法生成的 ByteBuf 与原始 ByteBuf 共用底层内存数据。

ByteBuf 除了 slice()duplicate() 方法之外,还有 retainedSlice()retainedDuplicate() 方法,这两个方法也可以实现零拷贝,而且还 增加了引用计数,避免在原始 ByteBuf 释放时意外释放共享的内存,所以更推荐调用这两个方法。

4. 零拷贝的优点

  • 降低 CPU 利用率,减少内存带宽消耗
  • 提升 I/O 密集型任务的性能

5. 总结

零拷贝并非一次拷贝都没有,它指的是尽量减少拷贝的数量,从而提升程序的性能。可优化的地方在于避免 数据在 内核空间 和 用户空间 的拷贝内核态 与 用户态 之间的上下文切换,在操作系统层面使用 sendfilemmap 就可以做到。在 Netty 中,实现零拷贝的方式有很多种,可以通过以下方式:

  • FileRegion 对象的 transfterTo() 方法
  • 直接内存
  • MappedByteBuffer
  • CompositeByteBuf
  • 切片和包装

http://www.kler.cn/a/613311.html

相关文章:

  • Java 大视界 -- 基于 Java 的大数据隐私计算在医疗影像数据共享中的实践探索(158)
  • 批量将多个 XPS 文档转换为 PDF 格式
  • 洛谷题单1-B2005 字符三角形-python-流程图重构
  • 安全性测试(Security Testing)
  • Manus AI 与多语言手写识别技术解析
  • 科技与人文的交融——当代科技对文化、艺术与社会伦理的深度影响
  • 提示词工程 — 科研论文笔记
  • 计算机视觉算法实战——半监督学习:技术与应用全景
  • 菜鸡前端计算机强基计划之CS50 第七课 python 入门—— Python 中文件操作专题学习
  • 配置基于接口的二层协议透明传输
  • Skynet 中 snlua 服务 init 细节
  • NX二次开发刻字功能——布尔运算
  • Matlab进阶绘图第73期-双组堆叠图
  • Python入门学习笔记 - 从环境搭建到基础语法
  • uni-app:自定义键盘
  • Leetcode 二叉树剪枝
  • 开源测试用例管理平台
  • 用户模块——自定义业务异常
  • Linux下的socket演示程序3(udp)
  • 新手村:逻辑回归-理解03:逻辑回归中的最大似然函数