用大片字符串理解Golang内存管理

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了用大片字符串理解Golang内存管理相关的知识,希望对你有一定的参考价值。

我正在为Go编写的Twitch.tv网站上的聊天机器人。

机器人的一个特征是奖励用户观看特定流的积分系统。此数据存储在SQLite3数据库中。

为了获得观众,机器人进行API调用以抽搐并收集流的所有当前观众。然后将这些观众放入一串字符串中。

总观看者的范围可以从几个到20,000或更多。

机器人做了什么

  • 进行API调用
  • 将所有观看者存储在一个字符串中
  • 对于每个观看者,bot会相应地迭代并添加点。
  • Bot在下一次迭代之前清除此切片

type Viewers struct {
    Chatters struct {
        CurrentModerators []string `json:"moderators"`
        CurrentViewers    []string `json:"viewers"`
    } `json:"chatters"`
}    

func RunPoints(timer time.Duration, modifier int, conn net.Conn, channel string) {
    database := InitializeDB() // Loads database through SQLite3 driver
    var Points int
    var allUsers []string
    for range time.NewTicker(timer * time.Second).C {
        currentUsers := GetViewers(conn, channel)
        tx, err := database.Begin()
        if err != nil {
            fmt.Println("Error starting points transaction: ", err)
        }

        allUsers = append(allUsers, currentUsers.Chatters.CurrentViewers...)
        allUsers = append(allUsers, currentUsers.Chatters.CurrentModerators...)

        for _, v := range allUsers {
            userCheck := UserInDB(database, v)
            if userCheck == false {
                statement, _ := tx.Prepare("INSERT INTO points (Username, Points) VALUES (?, ?)")
                statement.Exec(v, 1)
            } else {

                err = tx.QueryRow("Select Points FROM points WHERE Username = ?", v).Scan(&Points)
                if err != nil {

                } else {
                    Points = Points + modifier
                    statement, _ := tx.Prepare("UPDATE points SET Points = ? WHERE username = ?")
                    statement.Exec(Points, v)
                }
            }
        }

        tx.Commit()
        allUsers = allUsers[:0]
        currentUsers = Viewers{} // Clear Viewer object struct

    }

预期的行为

当吸引成千上万的观众时,我自然会期望系统资源变得非常高。这可以使用3.0 MB的RAM将机器人转为20 MB +。当然,成千上万的元素占用了大量的空间!

然而,还有其他事情发生。

实际行为

每次调用API时,RAM都会按预期增加。但是因为我清除了切片,我预计它会回落到它的“正常”3.0 MB的使用量。

但是,每次API调用的RAM使用量会增加,即使流的查看者总数有所增加,也不会降低。

因此,给了几个小时,机器人将很容易消耗100 + MB的ram,这对我来说似乎不对。


我在这里错过了什么?我对编程和CS一般都是新手,所以也许我正在尝试修复一些不是问题的东西。但这几乎听起来像是一个内存泄漏给我。

我已经尝试通过Golang的运行时库强制垃圾收集和释放内存,但这并没有解决它。

答案

要了解这里发生了什么,您需要了解切片的内部结构以及发生了什么。你应该从https://blog.golang.org/go-slices-usage-and-internals开始

简单回答:切片提供了对底层数组的一部分的视图,当您尝试截断切片时,您所做的只是减少对数组的视图,但底层数组不受影响并且仍占用同样多的内存。实际上,通过继续使用相同的数组,您永远不会减少您正在使用的内存量。

我鼓励你阅读它是如何工作的,但作为为什么没有释放实际内存的例子,看看这个简单程序的输出,该程序演示了如何更改切片不会截断分配的内存引擎盖下:https://play.golang.org/p/PLEZba8uD-L

另一答案

重新切片时:

allUsers = allUsers[:0]

所有元素仍在后备阵列中,无法收集。内存仍然被分配,这将在下一次运行中节省一些时间(它不必大量调整数组大小,节省了缓慢的分配),这是将其重新调整为零长度而不是仅仅转储它的点。

如果您希望将内存释放到GC,则需要完全转储它并每次创建一个新的切片。这会更慢,但在运行之间使用更少的内存。但是,这并不一定意味着您将看到该进程使用的内存更少。 GC收集未使用的堆对象,然后可能最终将该内存释放到操作系统,如果其他进程正在施加内存压力,操作系统最终可能会回收它。

以上是关于用大片字符串理解Golang内存管理的主要内容,如果未能解决你的问题,请参考以下文章

golang goroutine例子[golang并发代码片段]

Golang指针

超干货!彻底搞懂Golang内存管理和垃圾回收

golang中内存管理分配

golang中内存管理分配

golang代码片段(摘抄)