Русский
Русский
English
Статистика
Реклама

Pointers

Перевод Go Как использовать nil-значения без использования ссылочных типов

31.07.2020 00:18:01 | Автор: admin


Создано на базе изображений gopherize.me


Довольно часто из Go кода нам приходится работать с различными HTTP API или самим выполнять роль HTTP сервиса.


Один из частых случаев: получаем данные в виде структуры из базы данных, отправляем структуру внешнему API, в ответ получаем другую структуру, как-то её преобразуем и сохраняем в базу.


Другими словами: такая обработка не требует множества отдельных операций со структурами запроса и ответа.


Для API нормальна ситуация, когда в структурах запроса и ответа есть поля, которые могут быть nil и могут принимать какие-то не-nil значения. Такие структуры выглядят обычно так


type ApiResponse struct {  Code *string json:"code"`}

И, так как это ссылочный тип, то Go компилятор делает escape анализ и может перенести данную переменную в хип. В случае частого создания таких переменных мы получаем лишнюю нагрузку на GC и даже можем получить "утечку памяти", если GC не успевает освободить всю использованную память.


Что можно сделать в такой ситуации:


  • Изменить внешнее API так, чтобы не использовать nil значения. Иногда это допустимо, но изменение API не всегда хорошая идея: во-первых, это лишняя работа, во-вторых ошибки, которые могут появиться от такой переделки.
  • Изменить наш Go код так, чтобы мы могли принять nil значения, но не использую для этого ссылочные типы.

Для начала, давайте сравним разницу между работой со ссылочными типами и передачей переменных "по значению"


Все бенчмарки воспроизводимы и размещены здесь.


В Go коде мы обычно используем такие структуры со ссылочными типами


type pointerSmall struct { Field000 *string Field001 *string Field002 *string Field003 *string Field004 *string Field005 *string}

Давайте сравним их со структурами с типами, которые передаются по значению


type valueSmall struct { Field000 string Field001 string Field002 string Field003 string Field004 string Field005 string}

Структура со ссылками обработана с 0 аллокаций, аналогично структуре со значениями.
Здесь мы даже видим, что структура со ссылками быстрее по времени обработки.


Небольшое замечание: в данном бенчмарке мы видим две механики Go, которые объясняют эти (для кого-то неожиданные) результаты.


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


Второе копирование структур с типами, которые передаются по значению. На копирование структур тратится дополнительное время, что мы и видим в результатах теста.


BenchmarkPointerSmall-8    1000000000          0.295 ns/op        0 B/op        0 allocs/opBenchmarkValueSmall-8      184702404          6.51 ns/op        0 B/op        0 allocs/op

Давайте сделаем бенчмарк с вызовом функций и передачей туда структур со ссылками и указателями. Как мы видим, структуры со ссылками обрабатываются всё-ещё быстрее структур со значениями и по-прежнему с нулевым количеством аллокаций.


BenchmarkPointerSmallChain-8    1000000000          0.297 ns/op        0 B/op        0 allocs/opBenchmarkValueSmallChain-8      59185880         20.3 ns/op        0 B/op        0 allocs/op

В сервисах часто используется JSON кодирование и декодирование структур. Давайте сравним результаты кодирования и декодирования структур, используя jsoniter. В данном бенчмарке ситуация меняется. Структуры со значениями быстрее и лучше по использованию памяти на одну операцию, лучше по количеству аллокаций.


BenchmarkPointerSmallJSON-8       49522      23724 ns/op    14122 B/op       28 allocs/opBenchmarkValueSmallJSON-8         52234      22806 ns/op    14011 B/op       15 allocs/op

Давайте попробуем улучшить результаты кодирования и декодирования, используя easyjson. Почти все результаты для обеих структур лучше, кроме чуть большего использования памяти на одну операцию.


BenchmarkPointerSmallEasyJSON-8       64482      17815 ns/op    14591 B/op       21 allocs/opBenchmarkValueSmallEasyJSON-8         63136      17537 ns/op    14444 B/op       14 allocs/op

Предварительный вывод: если в вашем коде длинная цепочка обработки полученного значения с итоговым результатом, то иногда будет лучше использовать ссылочные типы. Но если цепочка обработки вашего кода короткая (кодирование/декодирование значения) то лучше использовать структуры со значениями.


Пойдём дальше. Иногда структуры растут в размере


type pointerBig struct { Field000 *string ... Field999 *string}type valueBig struct { Field000 string ... Field999 string}

Сделаем бенчмарк для этих структур. Далее мы видим, что для структуры со значениями обработка как и раньше даёт 0 аллокаций, но увеличилось время обработки (это нормально, т.к. структура стала больше). Так же, структура со ссылками потеряла преимущество: ненулевое количество аллокаций и значительно большее время обработки и использование памяти на одну операцию


BenchmarkPointerBig-8       36787      32243 ns/op    24192 B/op     1001 allocs/opBenchmarkValueBig-8        721375       1613 ns/op        0 B/op        0 allocs/op

Попробуем передать данные структуры через цепочку вызовов функций. Для структур со ссылками ничего не изменилось. Для структур со значениями незначительно выросло время обработки (но всё ещё меньше, чем для структуры со ссылками).


BenchmarkPointerBigChain-8       36607      31709 ns/op    24192 B/op     1001 allocs/opBenchmarkValueBigChain-8        351693       3216 ns/op        0 B/op        0 allocs/op

Попробуем сделать кодирование и декодирование. Структура со значениями лучше по всем параметрам


BenchmarkPointerBigJSON-8         250    4640020 ns/op  5326593 B/op     4024 allocs/opBenchmarkValueBigJSON-8           270    4289834 ns/op  4110721 B/op     2015 allocs/op

Попробуем улучшить результат, используя easyjson. Структура со значениями лучше во всём. Структура со ссылками обрабатывается лучше, чем в jsoniter.


BenchmarkPointerBigEasyJSON-8         364    3204100 ns/op  2357440 B/op     3066 allocs/opBenchmarkValueBigEasyJSON-8           380    3058639 ns/op  2302248 B/op     1063 allocs/op

Итоговый вывод: не делайте оптимизации на первом этапе разработки лучше предпочесть использовать структуры со значениями, чем структуры со ссылками. И только когда производительность перестала устраивать пройдите по цепочке обработки и попробуйте переключиться на передачу значений по ссылке в "горячих местах". Предпочтительно использовать кодогенераторы (easyjson и другие), чем обработку в коде в большинстве случаев получим результаты лучше.


Переключение на структуры со значениями


Переключение выглядит просто использовать Nullable типы. Пример из библиотеки sql sql.NullBool, sql.NullString и другие.


Так же, для типа потребуется описать функции кодирования и декодирования


func (n NullString) MarshalJSON() ([]byte, error) {    if !n.Valid {        return []byte("null"), nil    }    return jsoniter.Marshal(n.String)}func (n *NullString) UnmarshalJSON(data []byte) error {    if bytes.Equal(data, []byte("null")) {        *n = NullString{}        return nil    }    var res string    err := jsoniter.Unmarshal(data, &res)    if err != nil {        return err    }    *n = NullString{String: res, Valid: true}    return nil}

Как результат избавления от ссылочных типов в API я разработал библиотеку nan, с основными Nullable типами с функциями кодирования и декодирования для JSON, jsoniter, easyjson, gocql.


Удобство использования Nullable типов


И один из последних вопросов, которые можно задать про переключение на Nullable типы удобно ли их использовать.


Моё личное мнение удобно, у типов тот же паттерн использования что и у ссылок на переменые.


При использовании ссылки мы пишем


if a != nil && *a == "sometext" {

С Nullable типом мы пишем


if a.Valid && a.String == "sometext" {
Подробнее..
Категории: Gc , Api , Go , Nil , Pointers

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru