过多的 Go 语言重定向
过多的 Go 语言重定向
在 Go 语言中,为了提高效率,需要深入研究多层间接调用。
Go 中的许多函数都将 io.Reader
接口作为输入。这是一个明智的默认设置,允许数据流式传输,而不是将整个数据加载到内存中。但在某些情况下,我们需要字节数据。如果我们已经拥有这些字节,那么直接使用它们会更好。然而,这可能相当困难。
背景
我正在解码一些图像。我通过 C 绑定使用 libavif 和 libheif。主要是为了简化,我使用这些库的简单内存接口,这使得从 Go 获取数据到 C 变得容易得多。流式接口需要更多的工作,而且无论如何,这些库会在内部缓冲数据,从而创建另一个副本。并非每个解码器都能完全以流式方式工作。
因此,主要的实际操作函数接受 []byte
并将其传递给 C,并且有一个包装器以 Go 的方式使用 io.Reader
,该包装器在发送之前执行完整读取到临时缓冲区。碰巧的是,我的应用程序也在内部使用 []byte
,因为这是我从 libsqlite3 获取的数据 (同样,流式接口的连接要复杂得多),而且这也是使用 encoding/gob 进行 RPC 时获得的数据。我认为这不是一个不寻常的情况。
字节
我希望我的图像解码函数能够注意到给定的 io.Reader
实际上是一个 bytes.Reader
,这样我们就可以跳过复制。任何花时间浏览 Go 标准库的人都会注意到,类似的捷径很常见。接口会根据特定实现进行类型检查,然后采用优化的代码路径。我们可以进行检查,但这并不能立即提供帮助,因为 bytes.Reader
不会暴露其内部字节切片。
但它就在那里,我不会被拒绝的。
if br, ok := r.(*bytes.Reader); ok {
data = *(*[]byte)(unsafe.Pointer(br))
} else {
var buf bytes.Buffer
io.Copy(&buf, r)
data = buf.Bytes()
}
这在简单的测试中似乎有效,但在使用 image.Decode
函数时无效。仍然进行了复制。哪里出错了?
func Decode(r io.Reader) (Image, string, error) {
rr := asReader(r)
}
func asReader(r io.Reader) reader {
if rr, ok := r.(reader); ok {
return rr
}
return bufio.NewReader(r)
}
type reader interface {
io.Reader
Peek(int) ([]byte, error)
}
事实证明,Go 图像库会执行自己的类型检查,查找 Peek
函数,如果未找到,则将 reader 包装在 bufio.Reader
中。因此,bytes.Reader
永远不会以原始形式进入我们的函数。
现在,为什么 bytes.Reader
不实现 Peek
?它只是一个字节切片,绝对可以提前查看而不更改流状态。但它被忽略了,而是应用了这种解决方法。
仅仅知道我们有一个 bufio.Reader
是不够的,因为同样,它不会向我们暴露底层的 reader。 没关系,好吧,随便吧。 我是解封装大师。
type bufioReader struct {
buf []byte
rd io.Reader
}
if br, ok := r.(*bufio.Reader); ok {
insides := (*bufioReader)(unsafe.Pointer(br))
r = insides.rd
}
新的过程是查找 bufio.Reader
,如果是,则解包内部 reader。然后,与之前一样,如果它是 bytes.Reader
,我们提取字节。零拷贝的梦想还活着。
树
bufio.Reader
应该_可能_暴露底层 reader。
bytes.Reader
真的应该实现 Peek
。我非常确定它没有实现的原因是因为这是创建切片的只读视图的唯一方法。一个调皮的用户可以查看字节然后修改它们。唉。人们讨厌 const 污染,但我更讨厌这个。
bytes.Buffer
提供了 Bytes
函数,但仍然没有 Peek
,因此即使你知道这是必需的或有用的,它也不是一个简单的交换。
森林
我之前说过,Go 进行结构类型化的方式,以及标准库使用它的方式,创建了这些影子 API,其中受祝福的类型比其他类型工作得更好。几乎没有关于秘密要求的文档。但是,你能怪我想加入派对吗?
我认为有两种解释是可能的。将强制类型转换添加到语言中,并在整个标准库中使用它,从而证明该功能是有用的。或者悲观地看,每次类型转换都是设计上的疏忽。这种方法(通常,而不是我特定的魔法)仅在人们坚持使用标准类型的范围内才具有可扩展性。显然,专门针对第三方类型是不可行的。