Понимание управления памятью Golang с большим количеством строк - PullRequest
0 голосов
/ 07 сентября 2018

Я работаю над чат-ботом для сайта Twitch.tv, который написан на Go.

Одной из особенностей бота является система начисления очков, которая поощряет пользователей за просмотр определенного потока. Эти данные хранятся в базе данных SQLite3.

Чтобы получить зрителей, бот делает вызов API, чтобы дергаться и собирает всех текущих зрителей потока. Эти зрители затем помещают в кусочек строк.

Общее количество зрителей может варьироваться от пары до 20000 и более.

Что делает бот

  • Делает вызов API
  • Хранит всех зрителей в виде строки строк
  • Для каждого зрителя бот повторяет и добавляет очки соответственно.
  • Бот очищает этот срез перед следующей итерацией

Код

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 МБ ОЗУ до 20 МБ +. Конечно, тысячи элементов занимают много места!

Однако что-то еще происходит.

Фактическое поведение

Каждый раз, когда вызывается API, ОЗУ увеличивается, как и ожидалось. Но поскольку я очищаю фрагмент, я ожидаю, что он вернется к своему «нормальному» 3,0 МБ использования.

Однако объем использования ОЗУ увеличивается за один вызов API и не уменьшается, даже если общее число зрителей потока увеличивается.

Таким образом, , учитывая несколько часов, бот легко будет потреблять 100 + МБ ОЗУ, что мне не кажется правильным.


Что мне здесь не хватает? Я довольно новичок в программировании и CS в целом, поэтому, возможно, я пытаюсь исправить то, что не является проблемой. Но это звучит как утечка памяти для меня.

Я попытался форсировать сборку мусора и освободить память через библиотеку времени исполнения Голанга, но это не исправляет.

Ответы [ 2 ]

0 голосов
/ 07 сентября 2018

Когда вы меняете ломтик:

allUsers = allUsers[:0]

Все элементы все еще находятся в резервном массиве и не могут быть собраны. Память по-прежнему распределяется, что сэкономит некоторое время при следующем запуске (ей не нужно будет слишком сильно изменять размер массива, сохраняя медленные выделения), что является точкой ее уменьшения до нулевой длины, а не просто выгрузки.

Если вы хотите освободить память для ГХ, вам нужно будет просто полностью сбросить ее и каждый раз создавать новый фрагмент. Это будет медленнее, но использовать меньше памяти между запусками. Однако это не обязательно означает, что вы увидите меньше памяти, используемой процессом. GC собирает неиспользуемые объекты кучи, а затем может в конечном итоге освободить эту память для ОС, которая может в конечном итоге вернуть ее, если другие процессы применяют нагрузку на память.

0 голосов
/ 07 сентября 2018

Чтобы понять, что здесь происходит, вам нужно понять внутреннюю часть среза и что с ним происходит. Вы, вероятно, должны начать с https://blog.golang.org/go-slices-usage-and-internals

Чтобы дать краткий ответ: срез дает представление о части базового массива, и когда вы пытаетесь урезать свой срез, все, что вы делаете, - это уменьшаете представление, которое у вас есть над массивом, но основной Массив остается неизменным и все равно занимает столько же памяти. Фактически, продолжая использовать тот же массив, вы никогда не уменьшите объем используемой памяти.

Я бы рекомендовал вам прочитать о том, как это работает, но в качестве примера того, почему фактическая память не будет освобождена, взгляните на вывод этой простой программы, демонстрирующей, как изменения в срезе не будут усекаться. память, выделенная под капотом: https://play.golang.org/p/PLEZba8uD-L

...