猿问

在 Go 中处理包分配的最佳实践

我正在编写一个包,它在内部大量使用缓冲区进行临时存储。我有一个全局(但未导出)字节切片,它从 1024 个元素开始,并根据需要加倍增长。

但是,很可能我的包的用户会以这样的方式使用它,导致分配大缓冲区,然后停止使用该包,从而浪费大量分配的堆空间,我没有办法知道是否释放缓冲区(或者,因为这是 Go,让它被 GC 处理)。

我已经想到了三种可能的解决方案,但没有一个是理想的。我的问题是:在这种情况下,这些解决方案中的任何一个,或者可能是我没有想到的,是否是标准做法?有没有标准做法?还有其他想法吗?

  1. 算了。

那好吧。处理这个太难了,留下分配的内存并没有那么糟糕。

这种方法的问题很明显:它没有解决问题。

  1. 导出“我完成了”或“缩小内存使用量”功能。

导出一个用户可以调用的函数(智能调用显然取决于他们),这将释放包使用的内部存储空间。

这种方法的问题是双重的。首先,它为用户提供了一个更复杂、更不干净的界面。其次,用户可能无法或不切实际地知道何时调用这样的函数是明智的,因此无论如何它都可能毫无用处。

  1. 运行一个 goroutine,它会在包闲置一段时间后释放缓冲区,或者在缓冲区大小在一段时间内没有增加时缩小缓冲区(可能将长度减半)。

这种方法的问题主要在于它给调度程序带来了不必要的压力。显然,单个 goroutine 还不错,但如果这是公认的做法,那么如果您导入的每个包都在幕后执行此操作,它就不会很好地扩展。此外,如果您有一个时间敏感的应用程序,您可能不希望代码在您不知道的情况下运行(也就是说,您可能会假设该包在其函数未被调用时没有执行任何工作 - 一个合理的假设,我会说)。

所以……有什么想法吗?


凤凰求蛊
浏览 219回答 3
3回答

慕沐林林

一种常见的方法是让客户端将现有的 []byte(或其他)作为参数传递给某个调用/函数/方法。例如:// The returned slice may be a sub-slice of dst if dst was large enough// to hold the entire encoded block. Otherwise, a newly allocated slice// will be returned. It is valid to pass a nil dst.func Foo(dst []byte, whatever Bar) (ret []byte, err error)另一种方法是从缓存和/或例如池中获取新的 [] 字节(如果您更喜欢该概念的后一个名称),并依靠客户端将使用过的缓冲区返回到此类“回收站”。顺便说一句:考虑到这一点,你做得对。在可以合理地重用 []byte 缓冲区的地方,有可能降低 GC 负载,从而使您的程序性能更好。有时差异可能很关键。

慕码人2483693

您可以在每次操作结束时重新划分缓冲区。buffer = buffer[:0]然后,extendAndSliceBuffer如果需要增长,您的函数将具有最有可能可用的原始后备数组。如果没有,您将遭受新的分配,而您无论如何都可能会得到它extendAndSliceBuffer。总的来说,我认为更简洁的解决方案是像@jnml 所说的那样做,让用户在关心性能时传递他们自己的缓冲区。如果他们不关心性能,那么您不应该使用全局变量,而是根据需要简单地分配缓冲区,并在超出范围时将其释放。

料青山看我应如是

我有一个全局(但未导出)字节切片,它从 1024 个元素开始,并根据需要加倍增长。这就是你的问题。你的包中不应该有这样的全局变量。通常最好的方法是使用带有附加功能的导出结构。缓冲区应驻留在未导出的结构中。这样用户就可以实例化它并让垃圾收集器在他们放手时清理它。您还希望避免需要这样的全局变量,因为它会妨碍单元测试。单元测试应该能够像用户一样实例化导出的结构,并且每次测试时都这样做。还取决于什么样的缓冲你的需要,bytes.Buffer因为它已经提供了可能是有用的io.Reader和io.Writer功能。bytes.Buffer还会自动增长和缩小其缓冲区。在buffer.go 中,您会看到各种调用以b.Truncate(0)“重置以恢复空间”注释进行收缩。
随时随地看视频慕课网APP

相关分类

Go
我要回答