为什么使用 FilterOutputStream 拦截 FileInputStream 到 FileOutputStream 的传输要慢几个数量级?
Why is intercepting a FileInputStream to FileOutputStream transfer with FilterOutputStream orders of magnitude slower?
上下文
我正在尝试在数据传输过程中获得反馈。会出现不同的情况,但我正在处理的特殊情况是 FileInputStream 到 FileOutputStream 的复制。
实际的流复制循环是用 org.apache.commons.io.IOUtils 完成的。
请注意,我是一名经验丰富的开发人员,但我是一名 java 新手。 JVM的优化我不是很了解
问题
我将 FileOutputStream 包装在 java.io.FilterOutputStream 中以拦截传输并按如下方式计数:
FileInputStream input = new FileInputStream(new File("path"));
FileOutputStream output = new FileOutputStream(new File("path2"));
FilterOutputStream filterOutput = new FilterOutputStream(output);
IOUtils.copyLarge(input, filterOutput, new byte[32 * 1024]);
现在,当我这样做时,删除了实际的 "do something"(在上面的示例中,删除了我使用基本 FilterOutputStream 的实现,以免影响测试),复制一个 450Mb 的文件从5-10 秒(没有 FilterOutputStream 换行)到大约 8 分钟。
几个事实
- 在windowsx64 8核机器上测得
- 正在从本地 LAN 复制到我机器的 SSD
- 一个核心 100% 忙,直到操作结束
- 网络和磁盘几乎不忙 (1-2%)
- 我已经在我的文件流周围使用缓冲 input/output 流进行了测试,具有不同的缓冲区大小,但没有使用它们。
- 我改变了实际的数据缓冲区大小。
- None 以上两个变化被证明对有无之间的数量级差异有任何有意义的影响
FilterOutputStream 包装。
问题
为什么会出现这种情况?有什么办法可以解决这个问题吗?
我猜测 JVM 能够检测文件复制的标准模式并将其直接委托给 OS。这对我来说似乎有点奇怪,它将包装在缓冲流中,但不能用 FilterOutputStream 间接的写入方法来做到这一点。
现在我看到的唯一解决方法是在复制循环中直接实现一个监听器而不是管道输出流,但因为这需要重新实现循环而不是使用 Apache utils,然后添加并传递该监听器API 的几层,在我走那条路之前我正在寻找信息。
FilterOutputStream将按以下方法逐字节复制:
public void write(byte[] b,
int off,
int len)
throws IOException
Writes len bytes from the specified byte array starting at offset off to this output stream.
The write method of FilterOutputStream calls the write method of one argument on each byte to output.
Note that this method does not call the write method of its underlying input stream with the same arguments. Subclasses of FilterOutputStream should provide a more efficient implementation of this method.
A BufferedInputStream
是 FilterInputStream
,输出端同上。您可以使用它们代替过滤流并再次查看性能吗?这可能会弥补任何与 IO 相关的缓慢。
上下文
我正在尝试在数据传输过程中获得反馈。会出现不同的情况,但我正在处理的特殊情况是 FileInputStream 到 FileOutputStream 的复制。
实际的流复制循环是用 org.apache.commons.io.IOUtils 完成的。
请注意,我是一名经验丰富的开发人员,但我是一名 java 新手。 JVM的优化我不是很了解
问题
我将 FileOutputStream 包装在 java.io.FilterOutputStream 中以拦截传输并按如下方式计数:
FileInputStream input = new FileInputStream(new File("path"));
FileOutputStream output = new FileOutputStream(new File("path2"));
FilterOutputStream filterOutput = new FilterOutputStream(output);
IOUtils.copyLarge(input, filterOutput, new byte[32 * 1024]);
现在,当我这样做时,删除了实际的 "do something"(在上面的示例中,删除了我使用基本 FilterOutputStream 的实现,以免影响测试),复制一个 450Mb 的文件从5-10 秒(没有 FilterOutputStream 换行)到大约 8 分钟。
几个事实
- 在windowsx64 8核机器上测得
- 正在从本地 LAN 复制到我机器的 SSD
- 一个核心 100% 忙,直到操作结束
- 网络和磁盘几乎不忙 (1-2%)
- 我已经在我的文件流周围使用缓冲 input/output 流进行了测试,具有不同的缓冲区大小,但没有使用它们。
- 我改变了实际的数据缓冲区大小。
- None 以上两个变化被证明对有无之间的数量级差异有任何有意义的影响 FilterOutputStream 包装。
问题
为什么会出现这种情况?有什么办法可以解决这个问题吗?
我猜测 JVM 能够检测文件复制的标准模式并将其直接委托给 OS。这对我来说似乎有点奇怪,它将包装在缓冲流中,但不能用 FilterOutputStream 间接的写入方法来做到这一点。
现在我看到的唯一解决方法是在复制循环中直接实现一个监听器而不是管道输出流,但因为这需要重新实现循环而不是使用 Apache utils,然后添加并传递该监听器API 的几层,在我走那条路之前我正在寻找信息。
FilterOutputStream将按以下方法逐字节复制:
public void write(byte[] b, int off, int len) throws IOException
Writes len bytes from the specified byte array starting at offset off to this output stream. The write method of FilterOutputStream calls the write method of one argument on each byte to output.
Note that this method does not call the write method of its underlying input stream with the same arguments. Subclasses of FilterOutputStream should provide a more efficient implementation of this method.
A BufferedInputStream
是 FilterInputStream
,输出端同上。您可以使用它们代替过滤流并再次查看性能吗?这可能会弥补任何与 IO 相关的缓慢。