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