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

Memory Go中处理包分配的最佳实践,memory,go,package,allocation,Memory,Go,Package,Allocation,我正在写一个包,它大量使用内部缓冲区进行临时存储。我有一个全局(但不导出)字节片,从1024个元素开始,根据需要加倍增长 然而,我的包的用户很可能会以这样一种方式使用它,导致分配了一个大的缓冲区,但随后停止使用包,从而浪费了大量分配的堆空间,我也无法知道是否要释放缓冲区(或者,既然这样做了,就让它被GC’d) 我想到了三种可能的解决方案,但没有一种是理想的。我的问题是:在这样的情况下,这些解决方案中有没有一个是标准做法,或者我没有想到的?有没有标准的做法?还有其他想法吗 去他妈的 哦,好吧。处理

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

然而,我的包的用户很可能会以这样一种方式使用它,导致分配了一个大的缓冲区,但随后停止使用包,从而浪费了大量分配的堆空间,我也无法知道是否要释放缓冲区(或者,既然这样做了,就让它被GC’d)

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

  • 去他妈的
  • 哦,好吧。处理这件事太难了,把分配的内存放在一边也没那么糟糕

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

  • 导出了“我完成了”或“收缩内存使用”功能
  • 导出一个用户可以调用的函数(智能地调用它显然取决于他们),这将释放包使用的内部存储

    这种方法有两个问题。首先,它为用户提供了更复杂、更不干净的界面。第二,用户可能不可能也不实际地知道何时调用这样的函数是明智的,因此它可能毫无用处

  • 运行一个goroutine,在包闲置一段时间后释放缓冲区,或者在缓冲区的大小在一段时间内没有增加时收缩缓冲区(可能将长度减半)
  • 这种方法的问题主要在于它给调度器带来了不必要的压力。很明显,一个goroutine并没有那么糟糕,但是如果这是公认的做法,那么如果您导入的每个包都是在引擎盖下进行的,那么它的扩展性就不会很好。另外,如果您有一个时间敏感的应用程序,您可能不希望代码在您不知道的情况下运行(也就是说,您可能会假设包在其函数未被调用的情况下没有做任何工作—我认为这是一个合理的假设)

    所以。。。有什么想法吗


    注意:您可以看到现有的项目(相关的代码只有几十行)。

    一种常见的方法是让客户端将现有的[]字节(或任何内容)作为参数传递给某个调用/函数/方法。例如:

    // 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)
    
    ()

    另一种方法是从中获取一个新的[]字节,例如和/或(如果您更喜欢该概念的后一个名称),并依靠客户端将使用过的缓冲区返回到此类“回收站”

    顺便说一句:你这么想是对的。如果可以合理地重用[]字节缓冲区,则有可能降低GC负载,从而使程序性能更好。有时,差异可能是至关重要的

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

    这就是你的问题。你不应该在你的包里有这样一个全局的

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

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


    另外,根据您需要的缓冲区类型,
    bytes.buffer
    可能很有用,因为它已经提供了
    io.Reader
    io.Writer
    功能<代码>字节。缓冲区也会自动增大和缩小其缓冲区。在中,您将看到对
    b.Truncate(0)
    的各种调用,该调用使用注释“reset to recover space”(重置以恢复空间)进行收缩。

    您可以在每次操作结束时重新释放缓冲区

    buffer = buffer[:0]
    
    然后,如果函数
    extendAndSliceBuffer
    需要增长,则它很可能拥有原始的备份数组。否则,您将面临一个新的分配,当您执行
    extendAndSliceBuffer
    时,您可能会得到一个新的分配


    总的来说,我认为一个更干净的解决方案是像@jnml所说的那样,让用户传递他们自己的缓冲区,如果他们关心性能的话。如果他们不关心性能,那么您不应该使用全局变量,只需根据需要分配缓冲区,并在缓冲区超出范围时释放缓冲区。

    编写非线程安全的go代码通常是非常糟糕的。如果两个不同的goroutine同时调用修改缓冲区的函数,谁知道当它们完成时缓冲区将处于什么状态?如果用户认为分配性能是一个瓶颈,就让他们提供一个临时空间缓冲区。

    这些缓冲区是用户从未接触过的。该包实现了许多方便的功能,缓冲区用于临时存储。“我不认为这不是你所想的设计模式。”joshlf13我的建议仍然有效,不管缓冲区的类型如何。不要使用全局函数,给用户一些可以保留的东西,以便使用垃圾收集器。如果我想让用户处理更复杂的界面,我不妨使用导出“立即释放内存”函数的方法。密码。它应该能让你更好地理解我的意思。@joshlf13为什么不直接使用bytes.Buffer呢?我认为“传递你自己的缓冲区”模式在这里不起作用,因为它完全是内部的(用户永远看不到缓冲区的内容)。不过,我会更深入地研究缓存/池;这些看起来很有希望。