Комментарии 13
не хватает сравнения с simdjson-go. Думаю simdjson будет быстрее и нет необходимости с приседаниями писать самому декодер
Почему нет необходимости писать самому декодер?
Вот что примерно получается у SIMD: https://gist.github.com/ernado/3d2e256128dd173537825e6e845b87fc
SIMD в 10 раз медленней на таком json:
goos: linux
goarch: amd64
pkg: bench
cpu: AMD Ryzen 9 5950X 16-Core Processor
BenchmarkOTEL/Decode/jx-32 2385859 517.8 ns/op 965.61 MB/s 0 B/op 0 allocs/op
BenchmarkOTEL/Decode/simdjson-32 3970692 318.7 ns/op 87.86 MB/s 96 B/op 2 allocs/op
PASS
ok bench 3.327s
Да и то нет возможности написать аналогичный код, который сырой json в Body и значения атрибутов (values в Resource и Attributes) сохраняет.
да ошибся, там действительно нету unmarshal в структуру. Если будет время попробуйте пожалуйста https://github.com/bytedance/sonic
Если вот такую структуру использовать:
type OTEL struct {
Timestamp int64 `json:"Timestamp,string"`
Attributes map[string]json.RawMessage
Resource map[string]json.RawMessage
TraceID TraceID
SpanID SpanID
SeverityNumber byte
Body json.RawMessage
}
То получается 386.27 MB/s и 11 allocs/op. Мб можно как-то эффективнее, но я пока не придумал.
Целиком структуры для соника тут: https://gist.github.com/ernado/b8d576d3b20b73b452483e09297b9997
Скорее всего это разница из-за map[string]json.RawMessage vs кастомный Map из статьи.
Но если заменить на
type OTEL struct {
Timestamp int64 `json:"Timestamp,string"`
Attributes json.RawMessage
Resource json.RawMessage
TraceID TraceID
SpanID SpanID
SeverityNumber byte
Body json.RawMessage
}
То получается 561.37 MB/s.
Хочу поделиться, Решали задачу подобного плана, и одно время стоял joniter. Это самый быстрый пакет который нашли, но в итоге ушли в пользу самописного ручного «прохода»
В задаче было 4 типа сообщений с заранее известной формой, и они могли еще объединятся в «массивы». Скорость сообщений на входе в пиках 200-250к rps (примерно около 800мбит утилизировали в пиках)
Схема примерно такая получилась
Посмотреть первый байт, если там [, значит прилетел массив и он улетает на функцию которая посимвольно пролетает по всей строке и делит }, и возвращает обратно в поток для дальнейшего разбора
Если нет, значит измеряем количество байт и кидаем в один из 4 потоков на обработку (каждый тип сообщения имел разный размер)
Далее захватывали конкретные байты по местоположению и читаем выдергивая контент по типу.
Делали из размышлений: «а почему бы и нет» из разряда мозги прогреть, поэтому ожиданий никаких не было. В итоге joniter обогнали в 4 раза, а отрыв при большом потоке оказался еще больше
Мораль этой истории такова: joniter крутой пакет, но можно быстрее если сообщения стандартизированы
PS: метод преподготовки (деления массива на отдельные чанки и разделения типов сообщений) было изначально реализовано для joniter. Т.е мы точно выжали все что могли..
Я полностью согласен с таким подходом. Ведь если уже и так надо тратить время на сильную кастомизацию, то не было бы проще и лучше написать полностью свой парсер этого конкретного формата текста? Ведь задача стоит ускорить эту конкретную операцию, т.е. конкретное решение этой конкретной задачи всегда будет лучше.
А в чем суть вашей задачи? Локально на хосте лежит много файлов и их надо распарсить? Чтобы что? Аггрегировать, загрузить в базу?
Как вариант, задача — парсить много приходящих логов в секунду из кафки перед загрузкой в ClickHouse.
В этом случае, можно не бороться за высочайшую производительность. Всё равно ведь, в Clickhouse упретесь или в сеть
Возможно упремся, но зато меньше ресурсов потратим на json :)
Мышление как у админа локалхоста с гигабитной сетью.
Если можно сделать быстро и не тратить ресурсы, то почему бы и не сделать?
А освободившиеся ядра потратить на другую полезную работу или сэкономить денег.
А зачем там middleware? Почему нельзя складывать тупо тот джейсон, что приходит из Кафки, и прикрутить materialized views для того представления, которое нужно бизнесу, или кому там?

Как парсить гигабайты JSON в секунду на Go