bigywater.tech
Java · 247

FileChannel.transferTo:零拷贝究竟省掉了什么

transferTo 常被称为「零拷贝」。它到底比普通的 read/write 快多少、省下什么?这篇在一台 macOS 机器上把 1 GB 文件发给另一个进程的 socket,量时间与 CPU,并与缓冲区大小、mmap、Files.copy 对比。

0.73 → 0.17 s
1 GB 发往 socket:8 KB 堆缓冲区 → transferTo
0.2 s
同样数据用 1 MB 直接缓冲区 read/write
1120 MB
mmap 方式发送端的最大常驻内存

文件发往 socket

实测 · 本机执行输出
  read/write,8 KB 堆缓冲区               耗时中位 0.73 s(1399 MB/s),发送进程 user 0.23 s + sys 0.63 s,最大常驻内存 49 MB;接收端收到的字节数全部正确:True
  read/write,64 KB 直接缓冲区             耗时中位 0.31 s(3342 MB/s),发送进程 user 0.08 s + sys 0.30 s,最大常驻内存 47 MB;接收端收到的字节数全部正确:True
  read/write,1 MB 直接缓冲区              耗时中位 0.20 s(5131 MB/s),发送进程 user 0.05 s + sys 0.20 s,最大常驻内存 45 MB;接收端收到的字节数全部正确:True
  FileChannel.transferTo(一次要求发整个文件)  耗时中位 0.17 s(6063 MB/s),发送进程 user 0.04 s + sys 0.17 s,最大常驻内存 44 MB;接收端收到的字节数全部正确:True
  FileChannel.transferTo(每次最多 1 MB)  耗时中位 0.18 s(5685 MB/s),发送进程 user 0.04 s + sys 0.18 s,最大常驻内存 44 MB;接收端收到的字节数全部正确:True
  map 成 64 MB 一段再 write              耗时中位 0.27 s(3780 MB/s),发送进程 user 0.05 s + sys 0.27 s,最大常驻内存 1120 MB;接收端收到的字节数全部正确:True

transferTo 的返回值

实测 · 本机执行输出
  文件 1073741824 字节,transferTo 调用 1 次;单次返回最小 1073741824 字节,最大 1073741824 字节;总计 1073741824

文件到文件

实测 · 本机执行输出
  FileChannel.transferTo(文件)     最好一次 0.25 s(4295 MB/s),目标文件大小 1073741824 字节
  FileChannel.transferFrom       最好一次 0.30 s(3533 MB/s),目标文件大小 1073741824 字节
  Files.copy                     最好一次 0.58 s(1859 MB/s),目标文件大小 1073741824 字节
  read/write 1 MB 直接缓冲区          最好一次 0.34 s(3147 MB/s),目标文件大小 1073741824 字节

分步演示

零拷贝与 transferTo
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,macOS 10 核,本机回环;文件在页缓存里(1 GB 随机数据)
    • 没有验证:Linux 上的 sendfile / splice 行为与 CPU 数据、跨机器网络、文件不在页缓存时的磁盘读取、TLS 连接上 transferTo 不能零拷贝(需要加密)、strace 或 dtruss 看到的系统调用(macOS 上没有跑)、系统调用层面的「少一次拷贝」(只测了时间与 CPU,没有看内核)
    京ICP备2026067502号-1京公网安备11010502063487号