1

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

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

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

  1. 算了。

那好吧。处理这个太难了,并且留下分配的内存还不错

这种方法的问题很明显:它不能解决问题。

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

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

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

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

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

所以......有什么想法吗?

注意:您可以在此处查看现有项目(相关代码只有几十行)。

4

4 回答 4

0

编写非线程安全的 Go 代码通常是非常糟糕的形式。如果两个不同的 goroutine 同时调用修改缓冲区的函数,谁知道缓冲区完成时会处于什么状态?如果用户认为分配性能是瓶颈,只需让用户提供暂存空间缓冲区即可。

于 2013-08-05T05:12:33.907 回答
0

您可以在每次操作结束时重新调整缓冲区。

buffer = buffer[:0]

extendAndSliceBuffer然后,如果需要增长,您的函数将最有可能使用原始支持数组。如果没有,您将遭受新的分配,当您这样做时您可能会得到新的分配extendAndSliceBuffer

总的来说,我认为一个更清洁的解决方案是像@jnml 所说的那样,如果他们关心性能,让用户通过他们自己的缓冲区。如果他们不关心性能,那么您不应该使用全局 var 并根据需要简单地分配缓冲区,并在超出范围时将其释放。

于 2013-08-05T04:10:03.363 回答
0

我有一个全局(但未导出)字节片,我从 1024 个元素开始,并根据需要加倍增长。

还有你的问题。你的包中不应该有这样的全局变量。

通常,最好的方法是使用带有附加函数的导出结构。缓冲区应驻留在此未导出的结构中。这样,用户可以实例化它并让垃圾收集器在他们放手时清理它。

您还希望避免需要这样的全局变量,因为它会妨碍单元测试。单元测试应该能够像用户一样实例化导出的结构,并且每次测试都这样做。

还取决于您需要什么样的缓冲区,因为它已经提供和功能bytes.Buffer可能很有用。也会自动增长和缩小其缓冲区。在buffer.go 中,您会看到各种调用,这些调用会通过注释“重置以恢复空间”进行缩小。io.Readerio.Writerbytes.Bufferb.Truncate(0)

于 2013-08-04T21:51:50.553 回答
0

一种常见的方法是让客户端将现有的 []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 负载,从而使您的程序性能更好。有时差异可能很关键。

于 2013-08-04T21:42:50.103 回答