Обновить

Комментарии 25

Дополнительные шаги сборки, забытые команды go:generate, конфликты в пайплайнах.

Есть немало задач, которые требуют flow кодогенерации. К примеру grpc. или генерация кода по openapi спеке. и прочее и прочее. в том числе работа с json

мне кажется лучший выход - автоматизировать и наслаждаться.

А путь unsafe ... "Если в первом акте на стене висит ружье, то в последнем оно должно выстрелить". Впрочем сама статья и сравнения и решение хороши

Вы абсолютно правы, тут всегда вопрос выбора инструмента под задачу.

Я пришел к этому решению через боль: когда нужно было каждый час парсить сотню прайсов от поставщиков (до 6 млн позиций на файл) для крупного портала, вопрос скорости парсинга стал критическим. Было обидно осознавать, что «бутылочное горлышко» — это не сложная бизнес-логика или нехватка мощностей, а именно сам парсер, который просто не успевал переваривать валидные данные. Причем ситуация усугублялась тем, что форматы у поставщиков были абсолютно разные и постоянно менялись.

Если бы такой инструмент был под рукой тогда, я бы сэкономил массу времени и нервов. В общем, как вы и сказали — «ружье» выстрелило именно там, где это было нужно для выживания проекта

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

В версии 1.26 появился json2

С ним не сравнивали?

насколько я помню, это экспериментальная версия, по крайней мере в 1.26.3 
поэтому в репозитории она не присутствует. Но специально для ответа я провел тесты и получил

=== SilentJSON vs encoding/json v1 vs encoding/json/v2 (Go 1.26.3) ===

Platform: AMD Ryzen 9 7950×3D, Windows, GOEXPERIMENT=jsonv2

— Unmarshal 3M records (482 MB JSON, sequential) —

SilentJSON-32 1 753595900 ns/op 671.42 MB/s 65368888 B/op 6 allocs/op

StdJSON_v2-32 1 2698572700 ns/op 187.50 MB/s 1985055064 B/op 18910718 allocs/op

StdJSON_v1-32 1 3108066300 ns/op 162.80 MB/s 1985062216 B/op 18910832 allocs/op

— Unmarshal 100K records (parallel) —

SilentJSON_Parallel-32 3 4823467 ns/op 3293.48 MB/s 6021384 B/op 163 allocs/op

StdJSON_v1-32 3 80785100 ns/op 196.65 MB/s 2163085 B/op 220401 allocs/op

StdJSON_v2-32 3 82894933 ns/op 191.64 MB/s 19606984 B/op 820401 allocs/op

— Marshal 100K records —

SilentJSON-32 3 11557700 ns/op 1409.30 MB/s 0 B/op 0 allocs/op

StdJSON_v2-32 3 40116033 ns/op 396.00 MB/s 15892528 B/op 3 allocs/op

StdJSON_v1-32 3 41471367 ns/op 383.06 MB/s 15892528 B/op 3 allocs/op

— Stream Decode 3M records (482 MB JSON) —

SilentJSON_Stream-32 1 836256500 ns/op 605.05 MB/s 41405248 B/op 7714289 allocs/op

StdJSON_v2_Stream-32 1 2917844500 ns/op 173.41 MB/s 1985114304 B/op 18910791 allocs/op

StdJSON_v1_Stream-32 1 3703443800 ns/op 136.62 MB/s 106551440 B/op 5118604 allocs/op

Вы проделали очень крутую работу! Настоящий Highload!

К сожалению, ускорение библиотек на низком уровне часто нивелируется неоптимальной архитектурой на высоком. То самое “новая версия ОС ускорила открытие программ на 20%, тем временем программы потолстели на 200%”.

каждый час парсить сотню прайсов от поставщиков (до 6 млн позиций на файл) для крупного портала

С архитектурой портала что-то явно пошло не туда. Уверен что 99.9% позиций в этих прайсах не меняются так часто. Было бы правильнее передавать дельту изменений через API, а не тянуть по сети гигабайтные Json’ы сотнями каждый час. Вероятно после парсинга эти прайсы ещё и базу данных попусту нагружают.

Разные случаи бывают. много лет назад я работал в команде, там клиента мобильного писали индусы, back и web ui - мы. нормальный flow отправки обновлений с клиента так и не наладили - слали весь state как есть. там были не сотни мегабайт, но до пары мегабайт на порцию было ... много чего было.

И этот цирк длился до тех пор, пока не передали другой команде (нам).

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

Добро пожаловать в суровый энтерпрайз. Поставщики шлют то что умеют, а ты должен это как-то переваривать

Не смог проверить правду вы говорите или нет.
Ваша библиотека работает только на amd64.

я могу дополнить для вашей платформы поддержку, если напишите какую. Более того, буду благодарен, если поможете протестировать в своих условиях

arm (apple silicon)

можете проверить. попытался сделать с SIMD на ваш процессор. На виртуалке скорость нет смысла тестировать. simdjson-go вообще arm не поддерживает, поэтому цифры там невалидные будут.

а что насчет json с малым объемом, по типу 5-10 объектов? что там будет по аллокациям и скорости по сравнению с остальными

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

тут момент, что обычно алгоритмы если на большом объеме дают хороший выигрыш, то могут заметно проигрывать в мелких задачах на частых вызовах. Вот и хочется понять что там будет, когда надо лишь один жиденький реквест с json распарсить, но сделать это надо условно 100к раз в секунду на всех ядрах и тот же AVX2 может начать замедлять

справедливо, я добавил сравнение, не полное, но смысл понятен в описании в гите подправил график и табличку красивее. файлики по 5 записей.

LibraryThroughput (MB/s)Latency (ns/op)Memory Allocated Allocs/op

SilentJSON (Parallel) 735.98MB/s 877.7ns 25B 1

Sonic 317.21MB/s 2036ns 2632B 21

Standard (encoding/json) 84.61MB/s 7635ns 2528B 52

Если вы гоняете гигабайты json по сети, то проблема скорее всего в выборе формата, а не в парсере. Protobuf придумали не просто так

Совершенно верно. Но мы имеем то что имеем. Форматы не всегда мы выбираем. Это хорошо когда продукт не зависит от внешнего бизнеса. А внешнему бизнесу важнее другие вещи.

Зачем сравнение с бинарным протобаф? Я к тому что гигайбайты json в бинарном формате превратятся в мегабайты, возможно килобайты. Как при этом сравнивалась пропускная способность не совсем понятно. Все прочитал, но все равно слабо верится что бинарный протокол проигрывает текстовому.

Справедливо, я не корректно поставил информацию. Вернее было бы считать не мегобайты, а сколько объектов у нас распарсилось.

BenchmarkLargeScaleComparison/SilentJSON-32 241 5076327 ns/op 3129.43 MB/s 82334 B/op 132 allocs/op

BenchmarkLargeScaleComparison/Protobuf-32 36 32726719 ns/op 207.96 MB/s 39120496 B/op 1100019 allocs/op

видим 241 против 36 проходов абсолютно одинаковых наборов. можете сами протестировать на своих данных

я внес правки в статье в соответствии с данными

А сравните свои бенчмарки с https://github.com/buger/jsonparser, если не сложно. Я, в свое время, тоже пытался найти способ максимально эффективно обрабатывать множество json’ов, структура которых заранее неизвестна, эта библиотека дала максимальный эффект без необходимости изобретать велосипед (под влиянием rust’a тоже хотел минимум аллокаций и т.п.:))

1. BenchmarkNestedComparison

*Тест последовательной десериализации сложной структуры с глубокой вложенностью, ~3 млн записей. Данные содержат экранированные строки, null-значения и неизвестные поля.

| Библиотека | Время (ns/op) | Пропускная способность | Память (B/op) | Аллокации (allocs/op) |

| SilentJSON | 790,308,800 | 640.23 MB/s | 65,368,888 | 6 |

| Sonic | 1,203,687,900 | 420.36 MB/s | 886,626,392 | 10,612,816 |

| Jsoniter | 1,490,925,000 | 339.37 MB/s | 2,045,419,120 | 28,434,294 |

| Simdjson | 1,720,019,800 | 294.17 MB/s | 5,742,331,784 | 21 |

| Buger/jsonparser | 3,430,657,000 | 147.49 MB/s | 511,510,464 | 26,433,913 |

| Standard | 4,815,193,900 | 105.08 MB/s | 162,149,888 | 13,343,235 |

2. BenchmarkLargeScaleComparison

*Десериализация большого массива, состоящего из 100 000 объектов

| Библиотека | Время (ns/op) | Пропускная способность | Память (B/op) | Аллокации (allocs/op) |

| SilentJSON | 6,113,900 | 2598.34 MB/s | 19,189,576 | 24,472 |

| SonicParallel | 11,889,000 | 1336.19 MB/s | 55,370,048 | 569,222 |

| Sonic | 34,034,900 | 466.76 MB/s | 16,217,376 | 10,004 |

| Protobuf (для сравнения) | 35,280,500 | 192.91 MB/s | 39,120,520 | 1,100,019 |

| Simdjson_AST | 56,378,700 | 281.77 MB/s | 180,495,912 | 17 |

| Buger/jsonparser | 107,575,500 | 147.67 MB/s | 20,807,936 | 1,099,990 |

| Standard | 164,591,500 | 96.52 MB/s | 3,904,224 | 509,997 |

как то так

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации