我正在编写一个包,它在内部大量使用缓冲区来进行临时存储。我有一个全局(但不是导出)的字节切片,我从1024个元素开始,并根据需要增加一倍。
然而,我的包的用户很可能会以这样的方式使用它,导致分配大缓冲区,但随后停止使用包,从而浪费了大量的已分配堆空间,我会无法知道是否释放缓冲区(或者,因为这是Go,所以让它为GC)。
我想到了三种可能的解决方案,其中没有一种是理想的。我的问题是:在这样的情况下,这些解决方案中的任何一个,或者我没有想到的解决方案,标准做法是什么?有没有标准做法?还有其他想法吗?
这种方法的问题很明显:它无法解决问题。
导出一个用户可以调用的函数(并且智能地调用它显然取决于它们),这将释放包使用的内部存储。
这种方法的问题是双重的。首先,它为用户提供了更复杂,更不干净的界面。其次,用户知道何时调用这样的功能是明智的可能是不可能或不实际的,所以无论如何它可能都是无用的。
这种方法的问题主要在于它给调度程序带来了不必要的压力。显然单个goroutine并不是那么糟糕,但是如果这是公认的做法,如果你导入的每个软件包都在引擎盖下进行,那么它就不会很好地扩展。此外,如果您有一个时间敏感的应用程序,您可能不希望代码在您不知道它时运行(也就是说,您可能会认为该软件包在其函数未被调用时没有做任何工作 - 合理的假设,我会说。)
所以...任何想法?
注意:您可以看到现有项目here(相关代码只有几十行)。
答案 0 :(得分:0)
一种常见的方法是让客户端将现有的[]字节(或其他)作为参数传递给某个调用/函数/方法。例如:
// 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)
(Example)
另一种方法是从a获取一个新的[]字节,例如cache和/或例如pool(如果您更喜欢该概念的后一个名称)并依赖客户端返回使用缓冲区来处理这种“回收站”。
顺便说一句:你正在考虑这个问题。在可以合理地重用[]字节缓冲区的情况下,有可能降低GC负载,从而使程序更好地运行。有时差异可能很重要。答案 1 :(得分:0)
我启动了一个全局(但未导出)的字节切片 拥有1024个元素,并根据需要增加一倍。
这是你的问题。你的包中不应该有这样的全局。
通常,最好的方法是使用附加函数的导出结构。缓冲区应该驻留在未导出的结构中。这样,用户可以实例化它,让垃圾收集器在放开它时将其清理干净。
您还希望避免使用像这样的全局变量,因为它可能会妨碍单元测试。单元测试应该能够像用户一样实例化导出的结构,并且每次测试都要执行它。
另外,根据您需要的缓冲区类型,bytes.Buffer
可能很有用,因为它已经提供了io.Reader
和io.Writer
功能。 bytes.Buffer
也会自动增长和缩小其缓冲区。在buffer.go中,您会看到对b.Truncate(0)
的各种调用,这些调用通过评论“重置以恢复空间”而缩小。
答案 2 :(得分:0)
您可以在每次操作结束时重新关闭缓冲区。
buffer = buffer[:0]
然后,如果需要增长,您的函数extendAndSliceBuffer
将具有最可能的原始后备阵列。如果没有,您将遭受新的分配,无论如何,当您执行extendAndSliceBuffer
时,您可能会获得此分配。
总的来说,我认为一个更清洁的解决方案是像@jnml那样说,让用户通过他们自己的缓冲区,如果他们关心性能。如果他们不关心性能,那么你不应该使用全局var,只需根据需要分配缓冲区,当它超出范围时让它去。
答案 3 :(得分:0)
编写非线程安全的Go代码通常非常糟糕。如果两个不同的goroutine调用同时修改缓冲区的函数,谁知道缓冲区完成后将处于什么状态?如果用户认为分配性能是瓶颈,请让用户提供临时空间缓冲区。