Исходный адрес: 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.Маршал/Унмаршал
Решение также возможно. его Дубликат кода Меньше, но самый дорогой
В большинстве сценариев, какой из них использовать, имеет мало эффекта. Но как инженер, вы должны знать о его преимуществах и недостатках. Вот в чем разница. Анализ План Я надеюсь, что это вам поможет.