Потребление памяти с большим массивом с Echo или Gin Framework - PullRequest
0 голосов
/ 24 июня 2019

У меня проблема с памятью, когда я пытаюсь отправить большой массив с помощью Echo (и Gin тоже). После запроса память не свободна.

package main

import (
    "net/http"
    "strconv"

    "github.com/labstack/echo"
)

type User struct {
    Username  string
    Password  string
    Lastname  string
    Firstname string
}

func main() {
    e := echo.New()
    e.GET("/", func(c echo.Context) error {
        var user User
        users := make([]User, 0)

        for i := 0; i < 100000; i++ {
            user = User{
                Username:  "ffgfgfghhfghfhgfgfhgfghfghfhgfhgfh" + strconv.Itoa(i),
                Password:  "gjgjghjgjhgjhghjfrserhkhjhklljjkbhjvftxersgdghjjkhkljkbhftd",
                Lastname:  "njuftydfhgjkjlkjlkjlkhjkhu",
                Firstname: "jkggkjkl,,lm,kljkvgf"}

            users = append(users, user)
        }

        defer func() {
            users = nil
        }()
        return c.JSON(http.StatusOK, users)
    })
    e.Logger.Fatal(e.Start(":1323"))
}

Чтобы проверить, я запускаю запрос параллельно, и у меня есть эти результаты:

  • 1 запрос: 300Mo
  • 5 запросов: 1,5Go
  • 10 запросов: 3.1Go
  • подробнее: мой компьютер завис:)

Как я могу уменьшить потребление памяти?

Ответы [ 2 ]

1 голос
/ 25 июня 2019

Выделенная память не сразу возвращается в ОС, см. Невозможно освободить память, если она занята байтами. Буфер ; и освобождение неиспользуемой памяти?

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

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

Сделайте что-то вроде этого:

e.GET("/", func(c echo.Context) error {
    c.Response().WriteHeader(http.StatusOK)

    enc := json.NewEncoder(c.Response())
    for i := 0; i < 100000; i++ {
        user := User{
            Username:  "ffgfgfghhfghfhgfgfhgfghfghfhgfhgfh" + strconv.Itoa(i),
            Password:  "gjgjghjgjhgjhghjfrserhkhjhklljjkbhjvftxersgdghjjkhkljkbhfd",
            Lastname:  "njuftydfhgjkjlkjlkjlkhjkhu",
            Firstname: "jkggkjkl,,lm,kljkvgf",
        }
        if err := enc.Encode(user); err != nil {
            return err
        }
    }

    return nil
})

Здесь следует отметить одну вещь: приведенный выше код не отправляет массив JSON на выход, он отправляет серию объектов JSON. Если это вам не подходит и вам нужно отправить один JSON-массив, просто «обрамьте» данные и вставьте запятую между элементами:

e.GET("/", func(c echo.Context) error {
    resp := c.Response()
    resp.WriteHeader(http.StatusOK)

    if _, err := io.WriteString(resp, "["); err != nil {
        return err
    }
    enc := json.NewEncoder(resp)
    for i := 0; i < 100000; i++ {
        if i > 0 {
            if _, err := io.WriteString(resp, ","); err != nil {
                return err
            }
        }
        user := User{
            Username:  "ffgfgfghhfghfhgfgfhgfghfghfhgfhgfh" + strconv.Itoa(i),
            Password:  "gjgjghjgjhgjhghjfrserhkhjhklljjkbhjvftxersgdghjjkhkljkbhft",
            Lastname:  "njuftydfhgjkjlkjlkjlkhjkhu",
            Firstname: "jkggkjkl,,lm,kljkvgf",
        }
        if err := enc.Encode(user); err != nil {
            return err
        }
    }
    if _, err := io.WriteString(resp, "]"); err != nil {
        return err
    }

    return nil
})
0 голосов
/ 24 июня 2019

Я перехожу на решение @ icza.

Вот код:

package main

import (
    "encoding/json"
    "net/http"
    "strconv"

    "github.com/labstack/echo"
)

type User struct {
    Username  string
    Password  string
    Lastname  string
    Firstname string
}

func main() {
    e := echo.New()

    e.GET("/", func(c echo.Context) error {
        var user User
        users := make([]User, 0)

        for i := 0; i < 100000; i++ {
            user = User{
                Username:  "ffgfgfghhfghfhgfgfhgfghfghfhgfhgfh" + strconv.Itoa(i),
                Password:  "gjgjghjgjhgjhghjfrserhkhjhklljjkbhjvftxersgdghjjkhkljkbhftd",
                Lastname:  "njuftydfhgjkjlkjlkjlkhjkhu",
                Firstname: "jkggkjkl,,lm,kljkvgf"}

            users = append(users, user)
        }

        c.Response().WriteHeader(http.StatusOK)
        for _, user := range users {
            if err := json.NewEncoder(c.Response()).Encode(user); err != nil {
                return err
            }
            c.Response().Flush()
        }
        return nil
    })

    e.Logger.Fatal(e.Start(":1323"))
}

Благодаря этому у меня есть небольшое улучшение. Я перехожу с 3,5 ГБ до 2 ГБ. Но каждый раз, когда я отправляю запросы, используемая память увеличивается.

Почему ГХ не освобождает память после обслуживания запроса?

...