Рубрики
Uncategorized

Сводка анализа производительности для цикла и json. Немаршал

Автор оригинала: David Wong.

Исходный адрес: for-loop и json. Краткое описание анализа эффективности без промарширования

Предисловие

В проектах часто встречаются сценарии обработки данных с циклическим назначением обмена, особенно RPC. Формат взаимодействия с данными должен быть преобразован в Protobuf, и назначение неизбежно. Как правило, существуют следующие способы:

  • для
  • для диапазона
  • для диапазона

В это время мы сталкиваемся с “трудностью выбора”. Что лучше? Думайте о меньшем количестве кода и беспокойтесь о том, влияет ли производительность на производительность.

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

Функциональный код

...
type Person struct {
    Name   string `json:"name"`
    Age    int    `json:"age"`
    Avatar string `json:"avatar"`
    Type   string `json:"type"`
}

type AgainPerson struct {
    Name   string `json:"name"`
    Age    int    `json:"age"`
    Avatar string `json:"avatar"`
    Type   string `json:"type"`
}

const MAX = 10000

func InitPerson() []Person {
    var persons []Person
    for i := 0; i < MAX; i++ {
        persons = append(persons, Person{
            Name:   "EDDYCJY",
            Age:    i,
            Avatar: "https://github.com/EDDYCJY",
            Type:   "Person",
        })
    }

    return persons
}

func ForStruct(p []Person, count int) {
    for i := 0; i < count; i++ {
        _, _ = i, p[i]
    }
}

func ForRangeStruct(p []Person) {
    for i, v := range p {
        _, _ = i, v
    }
}

func JsonToStruct(data []byte, againPerson []AgainPerson) ([]AgainPerson, error) {
    err := json.Unmarshal(data, &againPerson)
    return againPerson, err
}

func JsonIteratorToStruct(data []byte, againPerson []AgainPerson) ([]AgainPerson, error) {
    var jsonIter = jsoniter.ConfigCompatibleWithStandardLibrary
    err := jsonIter.Unmarshal(data, &againPerson)
    return againPerson, err
}

Тестовый код

...
func BenchmarkForStruct(b *testing.B) {
    person := InitPerson()
    count := len(person)
    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        ForStruct(person, count)
    }
}

func BenchmarkForRangeStruct(b *testing.B) {
    person := InitPerson()

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        ForRangeStruct(person)
    }
}

func BenchmarkJsonToStruct(b *testing.B) {
    var (
        person = InitPerson()
        againPersons []AgainPerson
    )
    data, err := json.Marshal(person)
    if err != nil {
        b.Fatalf("json.Marshal err: %v", err)
    }

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        JsonToStruct(data, againPersons)
    }
}

func BenchmarkJsonIteratorToStruct(b *testing.B) {
    var (
        person = InitPerson()
        againPersons []AgainPerson
    )
    data, err := json.Marshal(person)
    if err != nil {
        b.Fatalf("json.Marshal err: %v", err)
    }

    b.ResetTimer()
    for i := 0; i < b.N; i++ {
        JsonIteratorToStruct(data, againPersons)
    }
}

результат теста

BenchmarkForStruct-4                    500000          3289 ns/op           0 B/op           0 allocs/op
BenchmarkForRangeStruct-4               200000          9178 ns/op           0 B/op           0 allocs/op
BenchmarkJsonToStruct-4                    100      19173117 ns/op     2618509 B/op       40036 allocs/op
BenchmarkJsonIteratorToStruct-4            300       4116491 ns/op     3694017 B/op       30047 allocs/op

По результатам тестирования рейтинг производительности выглядит следующим образом: для < для диапазона < json-итератор < кодирование/json. Далее давайте посмотрим, что вызывает этот рейтинг.

Сравнение производительности

для-петли

В результатах теста для диапазона Сравнения производительности для плохих. Почему это так? Здесь мы можем увидеть для диапазона Псевдо-реализация заключается в следующем:

for_temp := range
len_temp := len(for_temp)
for index_temp = 0; index_temp < len_temp; index_temp++ {
    value_temp = for_temp[index_temp]
    index = index_temp
    value = value_temp
    original body
}

Благодаря анализу псевдо-реализации мы можем знать, что для диапазона Сравнения для Были сделаны следующие вещи

Выражение

RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .

Перед началом цикла вычисляется выражение диапазона, и выражение “решение” используется для получения конечного значения диапазона.

Копировать

...
value_temp = for_temp[index_temp]
index = index_temp
value = value_temp
...

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

Резюме

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

Таким образом, на самом деле для диапазона На китайском языке мы можем использовать _ и T[i] Также можем достичь и для Примерно такой же производительности. Но это может быть и не так. для диапазона Конструкция предназначена.

Таким образом, на самом деле || для диапазона || На китайском языке мы можем использовать || _ || и || T[i] || Также можем достичь и || для || Примерно такой же производительности. Но это может быть и не так. || для диапазона || Конструкция предназначена.

кодирование/json

Совместимость JSON является самой медленной из трех схем. Почему?

Как мы все знаем, официальные кодирующие/json стандартные библиотеки реализуются с помощью большого количества отражений. Так что “медленный” тоже неизбежен. Смотрите следующий код:

...
func newTypeEncoder(t reflect.Type, allowAddr bool) encoderFunc {
    ...
    switch t.Kind() {
    case reflect.Bool:
        return boolEncoder
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        return intEncoder
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        return uintEncoder
    case reflect.Float32:
        return float32Encoder
    case reflect.Float64:
        return float64Encoder
    case reflect.String:
        return stringEncoder
    case reflect.Interface:
        return interfaceEncoder
    case reflect.Struct:
        return newStructEncoder(t)
    case reflect.Map:
        return newMapEncoder(t)
    case reflect.Slice:
        return newSliceEncoder(t)
    case reflect.Array:
        return newArrayEncoder(t)
    case reflect.Ptr:
        return newPtrEncoder(t)
    default:
        return unsupportedTypeEncoder
    }
}

Поскольку официальный стандартный запас-это определенная “проблема”, есть ли какое-либо другое решение? В настоящее время в сообществе существует в основном два типа программ. Следующим образом:

  • Предварительно скомпилированный сгенерированный код (определение типа заранее) может снизить затраты на производительность, вызванные отражением во время выполнения. Недостатком является добавление этапов предварительной генерации.
  • Оптимизируйте логику сериализации для максимальной производительности

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

  • json-итератор/go
  • почта ru/easyjson
  • pquerna/ffjson

json-итератор/go

Jsoniterator/go, который мы используем в нашем тестовом коде, в настоящее время чаще используется в сообществе.

Его использование на 100% совместимо со стандартной библиотекой, а производительность значительно улучшена. Давайте вкратце рассмотрим, как это делается, следующим образом:

отразить 2

Сокращение накладных расходов на планирование времени выполнения с помощью Modern-go/reflect 2

...
type StructDescriptor struct {
    Type   reflect2.Type
    Fields []*Binding
}

...
type Binding struct {
    levels    []int
    Field     reflect2.StructField
    FromNames []string
    ToNames   []string
    Encoder   ValEncoder
    Decoder   ValDecoder
}

type Extension interface {
    UpdateStructDescriptor(structDescriptor *StructDescriptor)
    CreateMapKeyDecoder(typ reflect2.Type) ValDecoder
    CreateMapKeyEncoder(typ reflect2.Type) ValEncoder
    CreateDecoder(typ reflect2.Type) ValDecoder
    CreateEncoder(typ reflect2.Type) ValEncoder
    DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder
    DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder
}
кэш структурного кодера/Декодера

Когда тип является структурой, вам нужно только один раз отразить Имя и тип, которые кэшируют кодер и декодер структуры.

var typeDecoders = map[string]ValDecoder{}
var fieldDecoders = map[string]ValDecoder{}
var typeEncoders = map[string]ValEncoder{}
var fieldEncoders = map[string]ValEncoder{}
var extensions = []Extension{}

....

fieldNames := calcFieldNames(field.Name(), tagParts[0], tag)
fieldCacheKey := fmt.Sprintf("%s/%s", typ.String(), field.Name())
decoder := fieldDecoders[fieldCacheKey]
if decoder == nil {
    decoder = decoderOfType(ctx.append(field.Name()), field.Type())
}
encoder := fieldEncoders[fieldCacheKey]
if encoder == nil {
    encoder = encoderOfType(ctx.append(field.Name()), field.Type())
}
Оптимизация синтаксического анализа текста

Резюме

По сравнению с официальной стандартной библиотекой, сторонняя библиотека jsoniterator/go Работает лучше во время выполнения. Вот почему это так быстро.

Следует отметить, что после Go1.10 map типы и стандартные библиотеки не сильно отличаются по производительности. Однако, например, типы struct и так далее по-прежнему имеют большее повышение производительности.

резюме

В этой статье мы сначала проверяем производительность, затем анализируем различные схемы, чтобы выяснить, почему скорость низкая. Наконец, при выборе решения его можно выбрать в соответствии с различными сценариями применения:

  • Более высокие требования к накладным расходам на производительность: выбор для Минимальных накладных расходов
  • Средние правила и Умеренные правила: Выбор для диапазона Будьте осторожны с большими объектами
  • Небольшое количество, небольшое занятие и контролируемое количество: выбор json.Маршал/Унмаршал Решение также возможно. его Дубликат кода Меньше, но самый дорогой

В большинстве сценариев, какой из них использовать, имеет мало эффекта. Но как инженер, вы должны знать о его преимуществах и недостатках. Вот в чем разница. Анализ План Я надеюсь, что это вам поможет.