当调用 io.CopyBuffer(writer, reader, buf)
时,它会尝试使用 writer
上的 io.ReaderFrom
接口(如果可用)。例如,net.TcpConn
实现 io.ReaderFrom
并将尝试使用类似于 splice
或 sendfile
的系统调用来避免用户空间中的额外复制。
这都是出于好意,并在从一个网络连接复制到另一个网络连接的情况下提供了更好的性能。然而,如果 reader
不是一个真正的文件,而只是实现了 io.Reader
的一些结构体,那么实际上它会回退到使用 io.Copy
。
这个问题在于,使用 io.CopyBuffer
的初衷是我们可以控制缓冲区并潜在地将缓冲区池化以避免垃圾回收。但通用的回退到 io.Copy
使得传入的缓冲区变得无用并产生额外的分配。
这种行为在下面的堆分析中表现得很清楚(左侧是传入的缓冲区,右侧是由通用ReadFrom回退(io.Copy)产生的分配):
对于不了解 TcpConn readfrom 内部原理的开发人员来说,这是非常令人惊讶的行为。作为使用 io.CopyBuffer 在热点路径上的一个明显优化,结果甚至比仅仅调用 io.Copy 更糟糕。
因此,建议添加一个类似于 io.ReaderFrom 的接口,但允许使用额外的缓冲区,因此命名为 io.ReaderFromBuffer
。这样,关于文件描述符复制的良好优化仍然可以发生,但在没有适用的情况下,通用ReadFrom 可以回退到 io.CopyBuffer
而不是 io.Copy
,以利用传入的缓冲区(如果有的话)。
7条答案
按热度按时间5anewei61#
See also #16474 .
noj0wjuj2#
核心问题是,如果扩展接口存在,它总是被调用。人们希望有一个类似于errors.Unwrap或http.ResponseController.Unwrap或fs.ErrNotImplemented的机制来表明该方法实际上没有实现直接的ReaderFrom/WriterTo,并且不应该在特定情况下使用。
j0pj023g3#
感谢上下文@ianlancetaylor!
正在思考...
人们想要的是一个类似于错误的机制,如errors.Unwrap或http.ResponseController.Unwrap或fs.ErrNotImplemented,以表明该方法实际上没有实现直接的ReaderFrom/WriterTo。
@carlmjohnson 对我来说是有意义的。然而,我同意这里的第16474条评论(评论):
...例如io.ErrNotSupported,但它可能会在大规模上破坏调用者。
考虑到v2可能永远不存在,至少对我来说,有一个非破坏性的、使
io.CopyBuffer
不那么令人惊讶的东西是有价值的。此外,这里第16474条评论(评论)也很有趣地提到:
从概念上讲,似乎ReaderFrom应该有一个缓冲区参数,如果不需要或为nil,则忽略它。这实际上是这个提案的内容,但以一种非API破坏性的方式。
我知道的关于
io.CopyBuffer
的两个用例是从阅读相关问题中得知的:我怀疑这是大多数人使用
io.CopyBuffer
的原因。通过在诸如os.File
、io.TcpConn
等中添加io.ReaderFromBuffer
接口并实现它,内核快速路径仍然可以在后台发生。如果不适用,它将优雅地降级为io.CopyBuffer
而不是io.Copy
。结果至少与不直观hack一样快,内存占用相同。
我不确定是否有人真的依赖于这一点。但在这个提案中提到了io: CopyBuffer应该避免ReadFrom/WriteTo #16474(评论)在一个测试用例中:
...另一个io.CopyBuffer行为意外的情况。测试依赖于在特定块大小上进行复制。
现在有两种情况:
io.ReaderFromBuffer
并 Package 了一个文件且快速路径可用(例如io.TcpConn),则不支持塑形。但我认为当它在内核级别处理时,塑形块大小的价值不大。对于内核驱动程序肯定是重要的,但对于用户空间级别的go来说不是那么重要。io.ReaderFromBuffer
只是一些代码,那么塑形仍然可以发生——结构体可以接受传递进来的缓冲区并根据需要对其进行操作。底线是,拥有
io.ReaderFromBuffer
仍然不能保证传递的缓冲区会被使用(如果有更快的路径可用),但它不会像io.ReaderFrom
那样导致意外的额外分配。bd1hkmkf4#
在现有方法中开始返回
io.ErrNotSupported
将破坏现有客户端,是的。你想要做的事情是创建一个新的接口,避免无谓的争论,称之为io.ReaderFromV2
和io.WriterToV2
,然后 那些 将被允许返回io.ErrNotSupported
,而io.Copy
/io.CopyBuffer
将知道并能够处理它。vof42yt15#
ErrNotSupported
是 #41198,它被接受,但我一直对实施它感到有点疑虑。eoxn13cs6#
是的。我在#41198上写道,该提案存在缺陷,因为“ErrNotSupported”是特定领域的。不支持http.FlushError与不支持io.ReaderFrom不同。对io.Copy的全面改革应该有特定于io的ErrNotSupported变量。
j91ykkif7#
https://go.dev/cl/475575提到了这个问题:
io: optimize copyBuffer to make use of the user-provided buffer for fallbacks