Эффективно генерировать строки JSON — json-gen

Go

Обзор

Многие операции на игровом сервере (включая игроков и не игроков) необходимо передавать в мидл-офис компании для сбора, обобщения и анализа данных в соответствии с потребностями операции. Сторона Zhongtai требует, чтобы данные передавались в формате JSON. В начале мы использовали стандартную библиотеку golang.encoding/json, обнаружили, что производительность неоптимальна (поскольку сериализация использует отражение, которое включает несколько выделений памяти). Поскольку исходный формат данныхmap[string]interface{}, и мне нужно создать одно поле за другим, поэтому я думаю, что длина конечной строки JSON может быть рассчитана в процессе построения, поэтому требуется только одно выделение памяти.

использовать

скачать:

$ go get github.com/darjun/json-gen

импорт:

import (
  jsongen "github.com/darjun/json-gen"
)

Удобнее использовать:

m := jsongen.NewMap()
m.PutUint("key1", 123)
m.PutInt("key2", -456)
m.PutUintArray("key3", []uint64{78, 90})
data := m.Serialize(nil)

dataТо есть окончательная сериализованная json String. Конечно, типы могут быть произвольно вложены. См. Кодgithub.

githubСравните с ним производительность стандартной библиотеки JSON в 10 раз!

Library Time/op(ns) B/op allocs/op
encoding/json 22209 6673 127
darjun/json-gen 3300 1152 1

выполнить

Сначала определите интерфейсValue, все значения, которые можно сериализовать в JSON, реализуют этот интерфейс:

type Value interface {
  Serialize(buf []byte) []byte
  Size() int
}
  • SerializeВы можете передать выделенную память, и этот метод добавит сериализованную строку JSON значения кbufПозади.
  • SizeВозвращает количество байтов, которое это значение в конечном итоге займет в строке JSON.

Классификация

Я разделил значения, которые можно сериализовать как строки JSON, на 4 категории:

  • QuotedValue: необходимо использовать в последней строке"Обернутое значение, такое как строка в golang.
  • UnquotedValue: не требуется в конечной строке"обернутое значение, например.uint/int/bool/float32Ждать.
  • Array: соответствует массиву в формате JSON.
  • Map: соответствует отображению в JSON.

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

QuotedValue

Нижняя часть основана наstringопределение типаQuotedValue:

type QuotedValue string

из-заQuotedValueВ конце концов в строке JSON будет 2", поэтому его размер: длина + 2. Давайте посмотримSerializeа такжеSizeРеализация метода:

func (q QuotedValue) Serialize(buf []byte) []byte {
  buf = append(buf, '"')
  buf = append(buf, []byte(q)...)
  return append(buf, '"')
}

func (q QuotedValue) Size() int {
  return len(q) + 2
}

UnquotedValue

То же самое основано наstringопределение типаUnquotedValue:

type UnquotedValue string

а такжеQuotedValueразница в том,UnquotedValueненужный"пакет,Serializeа такжеSize

Array

Array[]ValueArray

type Array []Value

Array,[]Size

func (a Array) Size() int {
  size := 0
  for _, e := range a {
    // 递归求元素的大小
    size += e.Size()
  }

  // for []
  size += 2
  if len(a) > 1 {
    // for ,
    size += len(a) - 1
  }

  return size
}

SerializeSerialize,[]

func (a Array) Serialize(buf []byte) []byte {
  if len(buf) == 0 {
    // 如果未传入分配好的空间,根据 Size 分配空间
    buf = make([]byte, 0, a.Size())
  }

  buf = append(buf, '[')
  count := len(a)
  for i, e := range a {
    buf = e.Serialize(buf)
    if i != count-1 {
      // 除了最后一个元素,每个元素后添加,
      buf = append(buf, ',')
    }
  }

  return append(buf, ']')
}

Array/MapAppendTypeAppendTypeArrayTypeuint/int/bool/float/Array/Map

string/Array/MapstrconvUnquotedValue"

func (a *Array) AppendUint(u uint64) {
  value := strconv.FormatUint(u, 10)

	*a = append(*a, UnquotedValue(value))
}

func (a *Array) AppendString(value string) {
	*a = append(*a, QuotedValue(escapeString(value)))
}

func (a *Array) AppendUintArray(u []uint64) {
	value := make([]Value, 0, len(u))
	for _, v := range u {
		value = append(value, UnquotedValue(strconv.FormatUint(v, 10)))
	}

	*a = append(*a, Array(value))
}

func (a *Array) AppendStringArray(s []string) {
	value := make([]Value, 0, len(s))
	for _, v := range s {
		value = append(value, QuotedValue(escapeString(v)))
	}

	*a = append(*a, Array(value))
}

Append*Array

Map

Mapmap[string]ValuemapMap

type Map struct {
  keys []string
  values []Value
}

Map

  • {}
  • "
  • :
  • ,

Size

func (m Map) Size() int {
  size := 0
	for i, key := range m.keys {
		// +2 for ", +1 for :
		size += len(key) + 2 + 1
		size += m.values[i].Size()
	}

	// +2 for {}
	size += 2

	if len(m.keys) > 1 {
		// for ,
		size += len(m.keys) - 1
	}

	return size
}

Serialize

func (m Map) Serialize(buf []byte) []byte {
	if len(buf) == 0 {
		buf = make([]byte, 0, m.Size())
	}

	buf = append(buf, '{')
	count := len(m.keys)
	for i, key := range m.keys {
		buf = append(buf, '"')
		buf = append(buf, []byte(key)...)
		buf = append(buf, '"')
		buf = append(buf, ':')
		buf = m.values[i].Serialize(buf)
		if i != count-1 {
			buf = append(buf, ',')
		}
	}
	return append(buf, '}')
}

ArrayMapMapArray/MapPutTypePutTypeArrayTypeuint/int/bool/float/Array/Map

func (m *Map) put(key string, value Value) {
	m.keys = append(m.keys, key)
	m.values = append(m.values, value)
}

func (m *Map) PutUint(key string, u uint64) {
	value := strconv.FormatUint(u, 10)

	m.put(key, UnquotedValue(value))
}

func (m *Map) PutUintArray(key string, u []uint64) {
	value := make([]Value, 0, len(u))
	for _, v := range u {
		value = append(value, UnquotedValue(strconv.FormatUint(v, 10)))
	}

	m.put(key, Array(value))
}